summaryrefslogtreecommitdiffstats
path: root/layout/base
diff options
context:
space:
mode:
Diffstat (limited to 'layout/base')
-rw-r--r--layout/base/AccessibleCaret.cpp378
-rw-r--r--layout/base/AccessibleCaret.h230
-rw-r--r--layout/base/AccessibleCaretEventHub.cpp702
-rw-r--r--layout/base/AccessibleCaretEventHub.h241
-rw-r--r--layout/base/AccessibleCaretLogger.h28
-rw-r--r--layout/base/AccessibleCaretManager.cpp1506
-rw-r--r--layout/base/AccessibleCaretManager.h442
-rw-r--r--layout/base/ArenaObjectID.h25
-rw-r--r--layout/base/AutoProfilerStyleMarker.h95
-rw-r--r--layout/base/Baseline.cpp105
-rw-r--r--layout/base/Baseline.h78
-rw-r--r--layout/base/CaretAssociationHint.h21
-rw-r--r--layout/base/ContainStyleScopeManager.cpp242
-rw-r--r--layout/base/ContainStyleScopeManager.h139
-rw-r--r--layout/base/DepthOrderedFrameList.cpp63
-rw-r--r--layout/base/DepthOrderedFrameList.h64
-rw-r--r--layout/base/DisplayPortUtils.cpp977
-rw-r--r--layout/base/DisplayPortUtils.h312
-rw-r--r--layout/base/FrameProperties.h435
-rw-r--r--layout/base/GeckoMVMContext.cpp217
-rw-r--r--layout/base/GeckoMVMContext.h70
-rw-r--r--layout/base/GeometryUtils.cpp498
-rw-r--r--layout/base/GeometryUtils.h68
-rw-r--r--layout/base/LayoutConstants.h108
-rw-r--r--layout/base/LayoutLogging.cpp29
-rw-r--r--layout/base/LayoutLogging.h64
-rw-r--r--layout/base/LayoutTelemetryTools.cpp177
-rw-r--r--layout/base/LayoutTelemetryTools.h90
-rw-r--r--layout/base/MVMContext.h69
-rw-r--r--layout/base/MediaEmulationData.h26
-rw-r--r--layout/base/MobileViewportManager.cpp757
-rw-r--r--layout/base/MobileViewportManager.h220
-rw-r--r--layout/base/MotionPathUtils.cpp762
-rw-r--r--layout/base/MotionPathUtils.h264
-rw-r--r--layout/base/OverflowChangedTracker.h208
-rw-r--r--layout/base/PositionedEventTargeting.cpp619
-rw-r--r--layout/base/PositionedEventTargeting.h41
-rw-r--r--layout/base/PresShell.cpp12203
-rw-r--r--layout/base/PresShell.h3287
-rw-r--r--layout/base/PresShellForwards.h245
-rw-r--r--layout/base/PresShellInlines.h83
-rw-r--r--layout/base/PresState.ipdlh60
-rw-r--r--layout/base/RelativeTo.h51
-rw-r--r--layout/base/RestyleManager.cpp3881
-rw-r--r--layout/base/RestyleManager.h606
-rw-r--r--layout/base/ScrollStyles.cpp48
-rw-r--r--layout/base/ScrollStyles.h44
-rw-r--r--layout/base/ScrollTypes.h73
-rw-r--r--layout/base/ShapeUtils.cpp249
-rw-r--r--layout/base/ShapeUtils.h151
-rw-r--r--layout/base/StackArena.cpp172
-rw-r--r--layout/base/StackArena.h92
-rw-r--r--layout/base/StaticPresData.cpp263
-rw-r--r--layout/base/StaticPresData.h176
-rw-r--r--layout/base/SurfaceFromElementResult.h104
-rw-r--r--layout/base/TouchManager.cpp476
-rw-r--r--layout/base/TouchManager.h84
-rw-r--r--layout/base/UnitTransforms.h388
-rw-r--r--layout/base/Units.h968
-rw-r--r--layout/base/ViewportUtils.cpp296
-rw-r--r--layout/base/ViewportUtils.h128
-rw-r--r--layout/base/WordMovementType.h16
-rw-r--r--layout/base/ZoomConstraintsClient.cpp281
-rw-r--r--layout/base/ZoomConstraintsClient.h55
-rw-r--r--layout/base/crashtests/1001237.html10
-rw-r--r--layout/base/crashtests/1009036.html15
-rw-r--r--layout/base/crashtests/1043163-1.html2
-rw-r--r--layout/base/crashtests/1061028.html9
-rw-r--r--layout/base/crashtests/1107508-1.html18
-rw-r--r--layout/base/crashtests/1116104.html15
-rw-r--r--layout/base/crashtests/1127198-1.html5
-rw-r--r--layout/base/crashtests/1140198.html16
-rw-r--r--layout/base/crashtests/1143535.html6
-rw-r--r--layout/base/crashtests/1153716.html19
-rw-r--r--layout/base/crashtests/1156588.html26
-rw-r--r--layout/base/crashtests/1162813.xhtml17
-rw-r--r--layout/base/crashtests/1163583.html14
-rw-r--r--layout/base/crashtests/118931-1.html7
-rw-r--r--layout/base/crashtests/121533-1.html11
-rw-r--r--layout/base/crashtests/123049-1.html12
-rw-r--r--layout/base/crashtests/1234622-1.html17
-rw-r--r--layout/base/crashtests/1235467-1.html8
-rw-r--r--layout/base/crashtests/123946-1.html10
-rw-r--r--layout/base/crashtests/1261351-iframe.html28
-rw-r--r--layout/base/crashtests/1261351.html7
-rw-r--r--layout/base/crashtests/1270797-1.html9
-rw-r--r--layout/base/crashtests/1270797-1.jpgbin0 -> 3595 bytes
-rw-r--r--layout/base/crashtests/1278455-1.html11
-rw-r--r--layout/base/crashtests/1286889.html2
-rw-r--r--layout/base/crashtests/128855-1.html8
-rw-r--r--layout/base/crashtests/1288608.html18
-rw-r--r--layout/base/crashtests/1288946-1.html9
-rw-r--r--layout/base/crashtests/1288946-2a.html13
-rw-r--r--layout/base/crashtests/1288946-2b.html13
-rw-r--r--layout/base/crashtests/1297835.html6
-rw-r--r--layout/base/crashtests/1299736-1.html15
-rw-r--r--layout/base/crashtests/1308793.svg31
-rw-r--r--layout/base/crashtests/1308848-1.html10
-rw-r--r--layout/base/crashtests/1308848-2.html10
-rw-r--r--layout/base/crashtests/133410-1.html27
-rw-r--r--layout/base/crashtests/1338772-1.html42
-rw-r--r--layout/base/crashtests/1340571.html15
-rw-r--r--layout/base/crashtests/1343139-1.html20
-rw-r--r--layout/base/crashtests/1343606.html39
-rw-r--r--layout/base/crashtests/1343937.html13
-rw-r--r--layout/base/crashtests/1352380.html9
-rw-r--r--layout/base/crashtests/1362423-1.html16
-rw-r--r--layout/base/crashtests/1381323.html10
-rw-r--r--layout/base/crashtests/1382534.html26
-rw-r--r--layout/base/crashtests/1388625-1.html10
-rw-r--r--layout/base/crashtests/1390389.html19
-rw-r--r--layout/base/crashtests/1391736.html23
-rw-r--r--layout/base/crashtests/1395591-1.html11
-rw-r--r--layout/base/crashtests/1395715-1.html17
-rw-r--r--layout/base/crashtests/1397398-1.html20
-rw-r--r--layout/base/crashtests/1397398-2.html20
-rw-r--r--layout/base/crashtests/1397398-3.html8
-rw-r--r--layout/base/crashtests/1398500.html21
-rw-r--r--layout/base/crashtests/1400438-1.html9
-rw-r--r--layout/base/crashtests/1400599-1.html7
-rw-r--r--layout/base/crashtests/1401739.html11
-rw-r--r--layout/base/crashtests/1401840.html10
-rw-r--r--layout/base/crashtests/1402476.html13
-rw-r--r--layout/base/crashtests/1404789-2.html2
-rw-r--r--layout/base/crashtests/1406562.html15
-rw-r--r--layout/base/crashtests/1409147.html20
-rw-r--r--layout/base/crashtests/1411138.html13
-rw-r--r--layout/base/crashtests/1414100.html9
-rw-r--r--layout/base/crashtests/1414303.html11
-rw-r--r--layout/base/crashtests/1419762.html15
-rw-r--r--layout/base/crashtests/1419802.html9
-rw-r--r--layout/base/crashtests/1420533.html12
-rw-r--r--layout/base/crashtests/1422908.html11
-rw-r--r--layout/base/crashtests/1425893.html6
-rw-r--r--layout/base/crashtests/1425959.html12
-rw-r--r--layout/base/crashtests/1428353.html15
-rw-r--r--layout/base/crashtests/1428892.html15
-rw-r--r--layout/base/crashtests/1429088.html8
-rw-r--r--layout/base/crashtests/1429961.html16
-rw-r--r--layout/base/crashtests/1429962.html12
-rw-r--r--layout/base/crashtests/1435015.html9
-rw-r--r--layout/base/crashtests/1437155.html13
-rw-r--r--layout/base/crashtests/143862-1a-inner.html19
-rw-r--r--layout/base/crashtests/143862-1a.html7
-rw-r--r--layout/base/crashtests/143862-1b-inner.html17
-rw-r--r--layout/base/crashtests/143862-1b.html7
-rw-r--r--layout/base/crashtests/143862-1c-inner.html17
-rw-r--r--layout/base/crashtests/143862-1c.html7
-rw-r--r--layout/base/crashtests/143862-2.html15
-rw-r--r--layout/base/crashtests/1439016.html18
-rw-r--r--layout/base/crashtests/1442018-1.html45
-rw-r--r--layout/base/crashtests/1442506.html10
-rw-r--r--layout/base/crashtests/1443027-1.html20
-rw-r--r--layout/base/crashtests/1448841-1.html17
-rw-r--r--layout/base/crashtests/1452839.html8
-rw-r--r--layout/base/crashtests/1453196.html15
-rw-r--r--layout/base/crashtests/1453342.html30
-rw-r--r--layout/base/crashtests/1453702.html16
-rw-r--r--layout/base/crashtests/1458121.html23
-rw-r--r--layout/base/crashtests/1461749.html19
-rw-r--r--layout/base/crashtests/1461812.html19
-rw-r--r--layout/base/crashtests/1462412.html9
-rw-r--r--layout/base/crashtests/1463940.html24
-rw-r--r--layout/base/crashtests/1464641.html25
-rw-r--r--layout/base/crashtests/1464737.html7
-rw-r--r--layout/base/crashtests/1466638.html13
-rw-r--r--layout/base/crashtests/1467519.html10
-rw-r--r--layout/base/crashtests/1467688.html16
-rw-r--r--layout/base/crashtests/1467964.html2
-rw-r--r--layout/base/crashtests/1469354.html16
-rw-r--r--layout/base/crashtests/1470499.html22
-rw-r--r--layout/base/crashtests/1472020.html11
-rw-r--r--layout/base/crashtests/1472027.html7
-rw-r--r--layout/base/crashtests/147320-1.html7
-rw-r--r--layout/base/crashtests/1477847.html17
-rw-r--r--layout/base/crashtests/148245-1.html11
-rw-r--r--layout/base/crashtests/1486521.html11
-rw-r--r--layout/base/crashtests/1489149.html52
-rw-r--r--layout/base/crashtests/1490037.html12
-rw-r--r--layout/base/crashtests/149014-1.html44
-rw-r--r--layout/base/crashtests/1494030.html22
-rw-r--r--layout/base/crashtests/1494332.html6
-rw-r--r--layout/base/crashtests/150431-1.html7
-rw-r--r--layout/base/crashtests/1505420.html19
-rw-r--r--layout/base/crashtests/1506163.html7
-rw-r--r--layout/base/crashtests/1506204.html17
-rw-r--r--layout/base/crashtests/1506314.html15
-rw-r--r--layout/base/crashtests/1507244.html25
-rw-r--r--layout/base/crashtests/1510080.html13
-rw-r--r--layout/base/crashtests/1510485.html7
-rw-r--r--layout/base/crashtests/1511442.html20
-rw-r--r--layout/base/crashtests/1511535.html9
-rw-r--r--layout/base/crashtests/1511563.html8
-rw-r--r--layout/base/crashtests/1516286-empty-mask.html14
-rw-r--r--layout/base/crashtests/1524382.html12
-rw-r--r--layout/base/crashtests/1524411.html15
-rw-r--r--layout/base/crashtests/1533885.html21
-rw-r--r--layout/base/crashtests/1534146.html15
-rw-r--r--layout/base/crashtests/1535945.html26
-rw-r--r--layout/base/crashtests/1539017.html15
-rw-r--r--layout/base/crashtests/1539303-iframe.html34
-rw-r--r--layout/base/crashtests/1539303.html13
-rw-r--r--layout/base/crashtests/1541679.html20
-rw-r--r--layout/base/crashtests/1547261.html3
-rw-r--r--layout/base/crashtests/1547391.html15
-rw-r--r--layout/base/crashtests/1548057.html42
-rw-r--r--layout/base/crashtests/1549867.html13
-rw-r--r--layout/base/crashtests/1553874.html17
-rw-r--r--layout/base/crashtests/1560328.html12
-rw-r--r--layout/base/crashtests/1566672.html20
-rw-r--r--layout/base/crashtests/1574101-1.html16
-rw-r--r--layout/base/crashtests/1574101-2.html10
-rw-r--r--layout/base/crashtests/1575908-1.html32
-rw-r--r--layout/base/crashtests/1576972-1.html23
-rw-r--r--layout/base/crashtests/1578844-1.html14
-rw-r--r--layout/base/crashtests/1578844-2.html20
-rw-r--r--layout/base/crashtests/1579953-1.html16
-rw-r--r--layout/base/crashtests/1580576.html11
-rw-r--r--layout/base/crashtests/1586600.html5
-rw-r--r--layout/base/crashtests/1599518.html9
-rw-r--r--layout/base/crashtests/1599532.html1
-rw-r--r--layout/base/crashtests/1606492.html21
-rw-r--r--layout/base/crashtests/1654315.html13
-rw-r--r--layout/base/crashtests/1676301-1.html16
-rw-r--r--layout/base/crashtests/1685146.html17
-rw-r--r--layout/base/crashtests/1689371.html5
-rw-r--r--layout/base/crashtests/1689912.html19
-rw-r--r--layout/base/crashtests/1690163.html7
-rw-r--r--layout/base/crashtests/1723200.html22
-rw-r--r--layout/base/crashtests/1729578.html26
-rw-r--r--layout/base/crashtests/1729581.html12
-rw-r--r--layout/base/crashtests/1734007.html15
-rw-r--r--layout/base/crashtests/1745860.html14
-rw-r--r--layout/base/crashtests/1746989.html11
-rw-r--r--layout/base/crashtests/1747277-1.html21
-rw-r--r--layout/base/crashtests/1752649.html4
-rw-r--r--layout/base/crashtests/1753779.html16
-rw-r--r--layout/base/crashtests/1755790.html31
-rw-r--r--layout/base/crashtests/176915-1.html10
-rw-r--r--layout/base/crashtests/1771503.html22
-rw-r--r--layout/base/crashtests/1789934.html16
-rw-r--r--layout/base/crashtests/1791883.html17
-rw-r--r--layout/base/crashtests/1797995.html13
-rw-r--r--layout/base/crashtests/1818036.html13
-rw-r--r--layout/base/crashtests/1819239.html33
-rw-r--r--layout/base/crashtests/1821469.html4
-rw-r--r--layout/base/crashtests/1849898-1.html40
-rw-r--r--layout/base/crashtests/191272-1.html13
-rw-r--r--layout/base/crashtests/199696-1.html33
-rw-r--r--layout/base/crashtests/217903-1.html5
-rw-r--r--layout/base/crashtests/223064-1.html11
-rw-r--r--layout/base/crashtests/234851-1.html14
-rw-r--r--layout/base/crashtests/234851-2.html35
-rw-r--r--layout/base/crashtests/241300-1.html5
-rw-r--r--layout/base/crashtests/243159-1.html4
-rw-r--r--layout/base/crashtests/243159-2.xhtml26
-rw-r--r--layout/base/crashtests/243519-1.html30
-rw-r--r--layout/base/crashtests/244490-1.html16
-rw-r--r--layout/base/crashtests/254367-1.html6
-rw-r--r--layout/base/crashtests/263359-1.html28
-rw-r--r--layout/base/crashtests/265027-1.html19
-rw-r--r--layout/base/crashtests/265736-1.html2
-rw-r--r--layout/base/crashtests/265736-2.html8
-rw-r--r--layout/base/crashtests/265899-1.html5
-rw-r--r--layout/base/crashtests/265973-1.html8
-rw-r--r--layout/base/crashtests/265986-1.html10
-rw-r--r--layout/base/crashtests/265999-1.html8
-rw-r--r--layout/base/crashtests/266222-1.html7
-rw-r--r--layout/base/crashtests/266360-1.html9
-rw-r--r--layout/base/crashtests/266445-1.html9
-rw-r--r--layout/base/crashtests/266445-2.html9
-rw-r--r--layout/base/crashtests/268157-1.html15
-rw-r--r--layout/base/crashtests/269566-1.html11
-rw-r--r--layout/base/crashtests/272647-1.html18
-rw-r--r--layout/base/crashtests/275746-1.html9
-rw-r--r--layout/base/crashtests/276053-1.html21
-rw-r--r--layout/base/crashtests/280708-1.html9
-rw-r--r--layout/base/crashtests/280708-2.html9
-rw-r--r--layout/base/crashtests/281333-1.html1
-rw-r--r--layout/base/crashtests/285212-1.html13
-rw-r--r--layout/base/crashtests/286813-1.html9
-rw-r--r--layout/base/crashtests/306940-1.html49
-rw-r--r--layout/base/crashtests/310267-1.xml32
-rw-r--r--layout/base/crashtests/310638-1.svg38
-rw-r--r--layout/base/crashtests/310638-2.html19
-rw-r--r--layout/base/crashtests/311661-1.xhtml31
-rw-r--r--layout/base/crashtests/311661-2.xhtml28
-rw-r--r--layout/base/crashtests/313086-1.xml28
-rw-r--r--layout/base/crashtests/317285-1.html1
-rw-r--r--layout/base/crashtests/317934-1-inner.html31
-rw-r--r--layout/base/crashtests/317934-1.html9
-rw-r--r--layout/base/crashtests/320459-1.html7
-rw-r--r--layout/base/crashtests/321058-1.xhtml4
-rw-r--r--layout/base/crashtests/321058-2.xhtml25
-rw-r--r--layout/base/crashtests/321077-1.xhtml6
-rw-r--r--layout/base/crashtests/321077-2.xhtml22
-rw-r--r--layout/base/crashtests/322436-1.html31
-rw-r--r--layout/base/crashtests/322678.html27
-rw-r--r--layout/base/crashtests/325024.html20
-rw-r--r--layout/base/crashtests/325218.xhtml25
-rw-r--r--layout/base/crashtests/325967-1.html29
-rw-r--r--layout/base/crashtests/325984-1.xhtml5
-rw-r--r--layout/base/crashtests/325984-2.html31
-rw-r--r--layout/base/crashtests/328944-1.xhtml23
-rw-r--r--layout/base/crashtests/329900-1.html15
-rw-r--r--layout/base/crashtests/330015-1.html14
-rw-r--r--layout/base/crashtests/331679-1.xhtml36
-rw-r--r--layout/base/crashtests/331679-2.xml19
-rw-r--r--layout/base/crashtests/331679-3.xml19
-rw-r--r--layout/base/crashtests/331883-1-inner.html30
-rw-r--r--layout/base/crashtests/331883-1.html16
-rw-r--r--layout/base/crashtests/335140-1.html12
-rw-r--r--layout/base/crashtests/336291-1.html19
-rw-r--r--layout/base/crashtests/336999-1.xhtml24
-rw-r--r--layout/base/crashtests/337066-1.xhtml22
-rw-r--r--layout/base/crashtests/337268-1.html45
-rw-r--r--layout/base/crashtests/337419-1.html23
-rw-r--r--layout/base/crashtests/337476-1.xhtml32
-rw-r--r--layout/base/crashtests/338703-1.html29
-rw-r--r--layout/base/crashtests/339651-1.html37
-rw-r--r--layout/base/crashtests/340093-1.xhtml11
-rw-r--r--layout/base/crashtests/341382-1.html22
-rw-r--r--layout/base/crashtests/341382-2.html9
-rw-r--r--layout/base/crashtests/341858-1.html14
-rw-r--r--layout/base/crashtests/342145-1.xhtml26
-rw-r--r--layout/base/crashtests/343293-1.xhtml19
-rw-r--r--layout/base/crashtests/343293-2.xhtml14
-rw-r--r--layout/base/crashtests/343540-1.html26
-rw-r--r--layout/base/crashtests/344057-1.xhtml9
-rw-r--r--layout/base/crashtests/344064-1-inner.xhtml13
-rw-r--r--layout/base/crashtests/344064-1.html9
-rw-r--r--layout/base/crashtests/344300-1-inner.xhtml36
-rw-r--r--layout/base/crashtests/344300-1.html9
-rw-r--r--layout/base/crashtests/344340-1.xhtml28
-rw-r--r--layout/base/crashtests/347898-1.html9
-rw-r--r--layout/base/crashtests/348126-1-inner.html28
-rw-r--r--layout/base/crashtests/348126-1.gifbin0 -> 980 bytes
-rw-r--r--layout/base/crashtests/348126-1.html9
-rw-r--r--layout/base/crashtests/348688-1.html24
-rw-r--r--layout/base/crashtests/348708-1.xhtml20
-rw-r--r--layout/base/crashtests/348729-1-inner.html29
-rw-r--r--layout/base/crashtests/348729-1.html6
-rw-r--r--layout/base/crashtests/349095-1.xhtml25
-rw-r--r--layout/base/crashtests/350267-1.html2
-rw-r--r--layout/base/crashtests/354133-1-inner.xhtml22
-rw-r--r--layout/base/crashtests/354133-1.html9
-rw-r--r--layout/base/crashtests/354766-1.xhtml19
-rw-r--r--layout/base/crashtests/355989-1.xhtml27
-rw-r--r--layout/base/crashtests/355993-1.xhtml26
-rw-r--r--layout/base/crashtests/356325-1.xhtml20
-rw-r--r--layout/base/crashtests/358729-1.xhtml52
-rw-r--r--layout/base/crashtests/360339-1.xhtml23
-rw-r--r--layout/base/crashtests/360339-2.xhtml28
-rw-r--r--layout/base/crashtests/363729-1.html3
-rw-r--r--layout/base/crashtests/363729-2.html18
-rw-r--r--layout/base/crashtests/363729-3.html20
-rw-r--r--layout/base/crashtests/365909-1.xhtml10
-rw-r--r--layout/base/crashtests/365909-2.xhtml10
-rw-r--r--layout/base/crashtests/366128-1.xhtml32
-rw-r--r--layout/base/crashtests/366271-1-frame.svg13
-rw-r--r--layout/base/crashtests/366271-1.html21
-rw-r--r--layout/base/crashtests/366967-1.html33
-rw-r--r--layout/base/crashtests/367015-1.html22
-rw-r--r--layout/base/crashtests/367243-1.html37
-rw-r--r--layout/base/crashtests/369176-1.html37
-rw-r--r--layout/base/crashtests/369547-1.html50
-rw-r--r--layout/base/crashtests/369547-2.html15
-rw-r--r--layout/base/crashtests/369945-1.xhtml42
-rw-r--r--layout/base/crashtests/371681-1.xhtml22
-rw-r--r--layout/base/crashtests/372237-1.html33
-rw-r--r--layout/base/crashtests/372550-1.html17
-rw-r--r--layout/base/crashtests/373628-1.html16
-rw-r--r--layout/base/crashtests/373628.html929
-rw-r--r--layout/base/crashtests/374297-1.html20
-rw-r--r--layout/base/crashtests/374297-2.html23
-rw-r--r--layout/base/crashtests/378325-1.html26
-rw-r--r--layout/base/crashtests/378682.html9
-rw-r--r--layout/base/crashtests/379419-1.xhtml12
-rw-r--r--layout/base/crashtests/379799-1.html31
-rw-r--r--layout/base/crashtests/380096-1.html4
-rw-r--r--layout/base/crashtests/382204-1.html21
-rw-r--r--layout/base/crashtests/383129-1-inner.xhtml22
-rw-r--r--layout/base/crashtests/383129-1.html9
-rw-r--r--layout/base/crashtests/384344-1-inner.html20
-rw-r--r--layout/base/crashtests/384344-1.html9
-rw-r--r--layout/base/crashtests/384392-1.xhtml27
-rw-r--r--layout/base/crashtests/384392-2.svg3
-rw-r--r--layout/base/crashtests/384649-1.xhtml31
-rw-r--r--layout/base/crashtests/385354.html18
-rw-r--r--layout/base/crashtests/385866-1.xhtml23
-rw-r--r--layout/base/crashtests/385880-1.xhtml8
-rw-r--r--layout/base/crashtests/386266-1.html28
-rw-r--r--layout/base/crashtests/386476.html12
-rw-r--r--layout/base/crashtests/387195-1.html7
-rw-r--r--layout/base/crashtests/387195-2.xhtml23
-rw-r--r--layout/base/crashtests/388715-1.html22
-rw-r--r--layout/base/crashtests/390976-1.html22
-rw-r--r--layout/base/crashtests/393661-1.html20
-rw-r--r--layout/base/crashtests/393801-1-inner.html781
-rw-r--r--layout/base/crashtests/393801-1.html7
-rw-r--r--layout/base/crashtests/394150-1.xhtml27
-rw-r--r--layout/base/crashtests/397011-1.xhtml13
-rw-r--r--layout/base/crashtests/398510-1.xhtml22
-rw-r--r--layout/base/crashtests/398733-1.html20
-rw-r--r--layout/base/crashtests/398733-2.html9
-rw-r--r--layout/base/crashtests/399132-1.xhtml16
-rw-r--r--layout/base/crashtests/399219-1.xhtml17
-rw-r--r--layout/base/crashtests/399365-1.html16
-rw-r--r--layout/base/crashtests/399676-1.xhtml7
-rw-r--r--layout/base/crashtests/399687-1.html38
-rw-r--r--layout/base/crashtests/399940-1.xhtml21
-rw-r--r--layout/base/crashtests/399951-1.html14
-rw-r--r--layout/base/crashtests/399994-1.html11
-rw-r--r--layout/base/crashtests/400445-1.xhtml22
-rw-r--r--layout/base/crashtests/400904-1.xhtml20
-rw-r--r--layout/base/crashtests/401734-1.html17
-rw-r--r--layout/base/crashtests/401734-2.html17
-rw-r--r--layout/base/crashtests/403048.html10
-rw-r--r--layout/base/crashtests/403175-1.html30
-rw-r--r--layout/base/crashtests/403245-1.html16
-rw-r--r--layout/base/crashtests/403454.html37
-rw-r--r--layout/base/crashtests/403569-1.xhtml29
-rw-r--r--layout/base/crashtests/403569-2.xhtml19
-rw-r--r--layout/base/crashtests/403569-3.xhtml25
-rw-r--r--layout/base/crashtests/404491-1.html5
-rw-r--r--layout/base/crashtests/404721-1.xhtml17
-rw-r--r--layout/base/crashtests/404721-2.xhtml18
-rw-r--r--layout/base/crashtests/405049-1.xhtml3
-rw-r--r--layout/base/crashtests/406675-1.html17
-rw-r--r--layout/base/crashtests/408292.html18
-rw-r--r--layout/base/crashtests/408299.html12
-rw-r--r--layout/base/crashtests/408450-1.xhtml7
-rw-r--r--layout/base/crashtests/409461-1.xhtml15
-rw-r--r--layout/base/crashtests/410967.html17
-rw-r--r--layout/base/crashtests/411870-1.html18
-rw-r--r--layout/base/crashtests/412651-1-frame.xhtml29
-rw-r--r--layout/base/crashtests/412651-1.html21
-rw-r--r--layout/base/crashtests/413587-1.svg11
-rw-r--r--layout/base/crashtests/415503.xhtml28
-rw-r--r--layout/base/crashtests/416107.xhtml26
-rw-r--r--layout/base/crashtests/419985.html29
-rw-r--r--layout/base/crashtests/420031-1.html8
-rw-r--r--layout/base/crashtests/420213-1.html6
-rw-r--r--layout/base/crashtests/420219-1.html22
-rw-r--r--layout/base/crashtests/420651-1.xhtml4
-rw-r--r--layout/base/crashtests/421203-1.xhtml5
-rw-r--r--layout/base/crashtests/421432.html14
-rw-r--r--layout/base/crashtests/422276.html18
-rw-r--r--layout/base/crashtests/423107-1.xhtml19
-rw-r--r--layout/base/crashtests/425981-1.html18
-rw-r--r--layout/base/crashtests/428138-1.html24
-rw-r--r--layout/base/crashtests/428448-1.html9
-rw-r--r--layout/base/crashtests/429088-1.html19
-rw-r--r--layout/base/crashtests/429088-2.html25
-rw-r--r--layout/base/crashtests/429865-1.html14
-rw-r--r--layout/base/crashtests/429881.html6
-rw-r--r--layout/base/crashtests/430569-1.html3
-rw-r--r--layout/base/crashtests/430569-2.html11
-rw-r--r--layout/base/crashtests/432752-1.svg27
-rw-r--r--layout/base/crashtests/433450-1.html19
-rw-r--r--layout/base/crashtests/436982-1.html7
-rw-r--r--layout/base/crashtests/437142-1.html25
-rw-r--r--layout/base/crashtests/439258-1.html20
-rw-r--r--layout/base/crashtests/439343.html2
-rw-r--r--layout/base/crashtests/444863-1.html25
-rw-r--r--layout/base/crashtests/444925-1.xhtml10
-rw-r--r--layout/base/crashtests/444967-1.html12
-rw-r--r--layout/base/crashtests/446328-iframe.html1
-rw-r--r--layout/base/crashtests/446328-top.html21
-rw-r--r--layout/base/crashtests/446328.gifbin0 -> 85 bytes
-rw-r--r--layout/base/crashtests/446328.html12
-rw-r--r--layout/base/crashtests/448488-1.html4
-rw-r--r--layout/base/crashtests/448543-1.html8
-rw-r--r--layout/base/crashtests/448543-2.html1
-rw-r--r--layout/base/crashtests/448543-3.html7
-rw-r--r--layout/base/crashtests/450319-1.xhtml32
-rw-r--r--layout/base/crashtests/453894-1.xhtml15
-rw-r--r--layout/base/crashtests/454751-1.xhtml20
-rw-r--r--layout/base/crashtests/455063-1.html6
-rw-r--r--layout/base/crashtests/455063-2.html6
-rw-r--r--layout/base/crashtests/455063-3.html6
-rw-r--r--layout/base/crashtests/455171-4.html8
-rw-r--r--layout/base/crashtests/455623-1.html19
-rw-r--r--layout/base/crashtests/457362-1.xhtml9
-rw-r--r--layout/base/crashtests/457514.html27
-rw-r--r--layout/base/crashtests/460389-1.html6
-rw-r--r--layout/base/crashtests/46043-1.html12
-rw-r--r--layout/base/crashtests/462392.html43
-rw-r--r--layout/base/crashtests/466763-1.html24
-rw-r--r--layout/base/crashtests/467881-1.html47
-rw-r--r--layout/base/crashtests/468491-1.html16
-rw-r--r--layout/base/crashtests/468555-1.xhtml9
-rw-r--r--layout/base/crashtests/468563-1.html7
-rw-r--r--layout/base/crashtests/468578-1.xhtml21
-rw-r--r--layout/base/crashtests/468645-3.xhtml5
-rw-r--r--layout/base/crashtests/469861-1.xhtml15
-rw-r--r--layout/base/crashtests/469861-2.xhtml15
-rw-r--r--layout/base/crashtests/470851-1.xhtml13
-rw-r--r--layout/base/crashtests/473042.xhtml1
-rw-r--r--layout/base/crashtests/474075.html12
-rw-r--r--layout/base/crashtests/477333-1.xhtml22
-rw-r--r--layout/base/crashtests/477731-1.html6
-rw-r--r--layout/base/crashtests/47843-1.html13
-rw-r--r--layout/base/crashtests/479114-1.html14
-rw-r--r--layout/base/crashtests/479360-1.xhtml16
-rw-r--r--layout/base/crashtests/480686-1.html13
-rw-r--r--layout/base/crashtests/481806-1.html14
-rw-r--r--layout/base/crashtests/483604-1.xhtml6
-rw-r--r--layout/base/crashtests/485501-1.html4
-rw-r--r--layout/base/crashtests/488390-1.xhtml18
-rw-r--r--layout/base/crashtests/489691.html20
-rw-r--r--layout/base/crashtests/490376-1.xhtml15
-rw-r--r--layout/base/crashtests/490559-1.html16
-rw-r--r--layout/base/crashtests/490747.html8
-rw-r--r--layout/base/crashtests/49122-1.html20
-rw-r--r--layout/base/crashtests/491547-1.xhtml20
-rw-r--r--layout/base/crashtests/491547-2.xhtml31
-rw-r--r--layout/base/crashtests/492014.xhtml4
-rw-r--r--layout/base/crashtests/492112-1.xhtml14
-rw-r--r--layout/base/crashtests/492163-1.xhtml21
-rw-r--r--layout/base/crashtests/495350-1.html9
-rw-r--r--layout/base/crashtests/496011-1.xhtml20
-rw-r--r--layout/base/crashtests/499741-1.xhtml1
-rw-r--r--layout/base/crashtests/499841-1.xhtml5
-rw-r--r--layout/base/crashtests/499858-1.xhtml5
-rw-r--r--layout/base/crashtests/500467-1.html23141
-rw-r--r--layout/base/crashtests/501878-1.html5
-rw-r--r--layout/base/crashtests/50257-1.html20
-rw-r--r--layout/base/crashtests/503936-1.html29
-rw-r--r--layout/base/crashtests/50395-1.html24
-rw-r--r--layout/base/crashtests/507119.html554
-rw-r--r--layout/base/crashtests/522374-1.html21
-rw-r--r--layout/base/crashtests/522374-2.html21
-rw-r--r--layout/base/crashtests/526378-1.xhtml28
-rw-r--r--layout/base/crashtests/534367-1.xhtml29
-rw-r--r--layout/base/crashtests/534368-1.xhtml14
-rw-r--r--layout/base/crashtests/534768-1.html23
-rw-r--r--layout/base/crashtests/534768-2.html22
-rw-r--r--layout/base/crashtests/535721-1.xhtml17
-rw-r--r--layout/base/crashtests/535911-1.xhtml16
-rw-r--r--layout/base/crashtests/536720.xhtml23
-rw-r--r--layout/base/crashtests/537562-1.xhtml10
-rw-r--r--layout/base/crashtests/537624-1.html18
-rw-r--r--layout/base/crashtests/537631-1.html5
-rw-r--r--layout/base/crashtests/538082-1.xhtml34
-rw-r--r--layout/base/crashtests/538207-1.xhtml14
-rw-r--r--layout/base/crashtests/538210-1.html16
-rw-r--r--layout/base/crashtests/538267-1.html18
-rw-r--r--layout/base/crashtests/540760.xhtml18
-rw-r--r--layout/base/crashtests/540771-1.xhtml18
-rw-r--r--layout/base/crashtests/541869-1.xhtml5
-rw-r--r--layout/base/crashtests/541869-2.html5
-rw-r--r--layout/base/crashtests/543648-1.html1
-rw-r--r--layout/base/crashtests/560447-1.html1
-rw-r--r--layout/base/crashtests/564063-1.html20
-rw-r--r--layout/base/crashtests/56746-1.html16
-rw-r--r--layout/base/crashtests/569018-1.html17
-rw-r--r--layout/base/crashtests/572003.xhtml3
-rw-r--r--layout/base/crashtests/572582-1.xhtml25
-rw-r--r--layout/base/crashtests/576649-1.html4
-rw-r--r--layout/base/crashtests/579655.html26
-rw-r--r--layout/base/crashtests/580129-1.html19
-rw-r--r--layout/base/crashtests/580494-1.html1
-rw-r--r--layout/base/crashtests/580834-1.xhtml5
-rw-r--r--layout/base/crashtests/589787.html27
-rw-r--r--layout/base/crashtests/591075-1.html2
-rw-r--r--layout/base/crashtests/591998-1.html2
-rw-r--r--layout/base/crashtests/595039-1.html1
-rw-r--r--layout/base/crashtests/597924-1.html16
-rw-r--r--layout/base/crashtests/606432-1.html24
-rw-r--r--layout/base/crashtests/609821-1.xhtml17
-rw-r--r--layout/base/crashtests/615146-1.html1
-rw-r--r--layout/base/crashtests/615781-1.xhtml22
-rw-r--r--layout/base/crashtests/616495-single-side-composite-color-border.html21
-rw-r--r--layout/base/crashtests/629035-1.html3
-rw-r--r--layout/base/crashtests/629908-1.html9
-rw-r--r--layout/base/crashtests/635329.html18
-rw-r--r--layout/base/crashtests/636229-1.html2
-rw-r--r--layout/base/crashtests/640272-empty.html0
-rw-r--r--layout/base/crashtests/640272-ref.html14
-rw-r--r--layout/base/crashtests/640272.html15
-rw-r--r--layout/base/crashtests/645193.html15
-rw-r--r--layout/base/crashtests/645572-1.html52
-rw-r--r--layout/base/crashtests/650475.xhtml14
-rw-r--r--layout/base/crashtests/650489.xhtml3
-rw-r--r--layout/base/crashtests/651342-1.html4
-rw-r--r--layout/base/crashtests/653133-1.html17
-rw-r--r--layout/base/crashtests/663295.html2
-rw-r--r--layout/base/crashtests/663662-1.html1
-rw-r--r--layout/base/crashtests/663662-2.html1
-rw-r--r--layout/base/crashtests/665837.html13
-rw-r--r--layout/base/crashtests/668579.html10
-rw-r--r--layout/base/crashtests/668941.xhtml16
-rw-r--r--layout/base/crashtests/670226.html10
-rw-r--r--layout/base/crashtests/675246-1.xhtml8
-rw-r--r--layout/base/crashtests/690247-1.html2
-rw-r--r--layout/base/crashtests/690619-1.html1
-rw-r--r--layout/base/crashtests/691118-1.html24
-rw-r--r--layout/base/crashtests/695861.html9
-rw-r--r--layout/base/crashtests/695964-1.svg1
-rw-r--r--layout/base/crashtests/698335.html2
-rw-r--r--layout/base/crashtests/699353-1.html18
-rw-r--r--layout/base/crashtests/701504.html24
-rw-r--r--layout/base/crashtests/707098.html6
-rw-r--r--layout/base/crashtests/709536-1.xhtml1
-rw-r--r--layout/base/crashtests/722137.html18
-rw-r--r--layout/base/crashtests/725535.html8
-rw-r--r--layout/base/crashtests/727601.html3
-rw-r--r--layout/base/crashtests/735943.html38
-rw-r--r--layout/base/crashtests/736389-1.xhtml47
-rw-r--r--layout/base/crashtests/736924-1.html23
-rw-r--r--layout/base/crashtests/749816-1.html15
-rw-r--r--layout/base/crashtests/763223-1.html6
-rw-r--r--layout/base/crashtests/763702.xhtml9
-rw-r--r--layout/base/crashtests/767593-1.html7
-rw-r--r--layout/base/crashtests/767593-2.html7
-rw-r--r--layout/base/crashtests/770381-1.html12
-rw-r--r--layout/base/crashtests/772306.html40
-rw-r--r--layout/base/crashtests/788360.html6
-rw-r--r--layout/base/crashtests/793848.html28
-rw-r--r--layout/base/crashtests/795646.html7
-rw-r--r--layout/base/crashtests/802902.html10
-rw-r--r--layout/base/crashtests/806056-1.html16
-rw-r--r--layout/base/crashtests/806056-2.html18
-rw-r--r--layout/base/crashtests/812665.html6
-rw-r--r--layout/base/crashtests/813372-1.html52
-rw-r--r--layout/base/crashtests/817219-iframe.html35
-rw-r--r--layout/base/crashtests/817219.html22
-rw-r--r--layout/base/crashtests/818454.html24
-rw-r--r--layout/base/crashtests/822865.html4
-rw-r--r--layout/base/crashtests/824300.html13
-rw-r--r--layout/base/crashtests/824862.html5
-rw-r--r--layout/base/crashtests/826163.html11
-rw-r--r--layout/base/crashtests/827192.html9
-rw-r--r--layout/base/crashtests/830138-1.html17
-rw-r--r--layout/base/crashtests/830192-1.html31
-rw-r--r--layout/base/crashtests/830299-1.html27
-rw-r--r--layout/base/crashtests/833604-1.html18
-rw-r--r--layout/base/crashtests/835056.html19
-rw-r--r--layout/base/crashtests/836990-1.html12
-rw-r--r--layout/base/crashtests/840480.html44
-rw-r--r--layout/base/crashtests/842114.html6
-rw-r--r--layout/base/crashtests/847242.html13
-rw-r--r--layout/base/crashtests/852293.html67
-rw-r--r--layout/base/crashtests/859526-1.html7
-rw-r--r--layout/base/crashtests/859630-1.html7
-rw-r--r--layout/base/crashtests/860579-1.html21
-rw-r--r--layout/base/crashtests/866588.html25
-rw-r--r--layout/base/crashtests/876092.html29
-rw-r--r--layout/base/crashtests/876221.html39
-rw-r--r--layout/base/crashtests/89101-1.html22
-rw-r--r--layout/base/crashtests/89358-1.html10
-rw-r--r--layout/base/crashtests/897852.html9
-rw-r--r--layout/base/crashtests/898913.html24
-rw-r--r--layout/base/crashtests/90205-1.html15
-rw-r--r--layout/base/crashtests/926728.html13
-rw-r--r--layout/base/crashtests/930381.html122
-rw-r--r--layout/base/crashtests/931450.html10
-rw-r--r--layout/base/crashtests/931460-1.html5
-rw-r--r--layout/base/crashtests/931464.html18
-rw-r--r--layout/base/crashtests/935765-1.html9
-rw-r--r--layout/base/crashtests/936988-1.html9
-rw-r--r--layout/base/crashtests/942690.html15
-rw-r--r--layout/base/crashtests/973390-1.html7
-rw-r--r--layout/base/crashtests/989994-1.html5
-rw-r--r--layout/base/crashtests/99776-1.html9
-rw-r--r--layout/base/crashtests/crashtests.list579
-rw-r--r--layout/base/gtest/TestAccessibleCaretEventHub.cpp785
-rw-r--r--layout/base/gtest/TestAccessibleCaretManager.cpp848
-rw-r--r--layout/base/gtest/moz.build26
-rw-r--r--layout/base/metrics.yaml50
-rw-r--r--layout/base/moz.build188
-rw-r--r--layout/base/nsAutoLayoutPhase.cpp90
-rw-r--r--layout/base/nsAutoLayoutPhase.h49
-rw-r--r--layout/base/nsBidiPresUtils.cpp2499
-rw-r--r--layout/base/nsBidiPresUtils.h587
-rw-r--r--layout/base/nsCSSColorUtils.cpp202
-rw-r--r--layout/base/nsCSSColorUtils.h49
-rw-r--r--layout/base/nsCSSFrameConstructor.cpp12018
-rw-r--r--layout/base/nsCSSFrameConstructor.h2159
-rw-r--r--layout/base/nsCaret.cpp766
-rw-r--r--layout/base/nsCaret.h284
-rw-r--r--layout/base/nsChangeHint.h513
-rw-r--r--layout/base/nsCompatibility.h18
-rw-r--r--layout/base/nsCounterManager.cpp552
-rw-r--r--layout/base/nsCounterManager.h350
-rw-r--r--layout/base/nsDocumentViewer.cpp3514
-rw-r--r--layout/base/nsFrameManager.cpp250
-rw-r--r--layout/base/nsFrameManager.h100
-rw-r--r--layout/base/nsFrameTraversal.cpp305
-rw-r--r--layout/base/nsFrameTraversal.h122
-rw-r--r--layout/base/nsGenConList.cpp229
-rw-r--r--layout/base/nsGenConList.h132
-rw-r--r--layout/base/nsIDocumentViewerPrint.h74
-rw-r--r--layout/base/nsILayoutHistoryState.idl120
-rw-r--r--layout/base/nsIPercentBSizeObserver.h33
-rw-r--r--layout/base/nsIPreloadedStyleSheet.idl16
-rw-r--r--layout/base/nsIReflowCallback.h34
-rw-r--r--layout/base/nsIStyleSheetService.idl71
-rw-r--r--layout/base/nsLayoutDebugger.cpp276
-rw-r--r--layout/base/nsLayoutHistoryState.cpp162
-rw-r--r--layout/base/nsLayoutUtils.cpp10003
-rw-r--r--layout/base/nsLayoutUtils.h3221
-rw-r--r--layout/base/nsPresArena.cpp185
-rw-r--r--layout/base/nsPresArena.h67
-rw-r--r--layout/base/nsPresArenaObjectList.h26
-rw-r--r--layout/base/nsPresContext.cpp3137
-rw-r--r--layout/base/nsPresContext.h1461
-rw-r--r--layout/base/nsPresContextInlines.h22
-rw-r--r--layout/base/nsQuoteList.cpp165
-rw-r--r--layout/base/nsQuoteList.h100
-rw-r--r--layout/base/nsRefreshDriver.cpp3323
-rw-r--r--layout/base/nsRefreshDriver.h735
-rw-r--r--layout/base/nsRefreshObservers.cpp55
-rw-r--r--layout/base/nsRefreshObservers.h113
-rw-r--r--layout/base/nsStyleChangeList.cpp72
-rw-r--r--layout/base/nsStyleChangeList.h55
-rw-r--r--layout/base/nsStyleSheetService.cpp337
-rw-r--r--layout/base/nsStyleSheetService.h76
-rw-r--r--layout/base/tests/Ahem.ttfbin0 -> 12480 bytes
-rw-r--r--layout/base/tests/accessiblecaret_magnifier.html91
-rw-r--r--layout/base/tests/border_radius_hit_testing_iframe.html27
-rw-r--r--layout/base/tests/browser.toml61
-rw-r--r--layout/base/tests/browser_bug1701027-1.js136
-rw-r--r--layout/base/tests/browser_bug1701027-2.js126
-rw-r--r--layout/base/tests/browser_bug1757410.js62
-rw-r--r--layout/base/tests/browser_bug1787079.js88
-rw-r--r--layout/base/tests/browser_bug1791083.js81
-rw-r--r--layout/base/tests/browser_bug617076.js71
-rw-r--r--layout/base/tests/browser_css_registered_property.js93
-rw-r--r--layout/base/tests/browser_disableDialogs_onbeforeunload.js64
-rw-r--r--layout/base/tests/browser_onbeforeunload_only_after_interaction.js75
-rw-r--r--layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js96
-rw-r--r--layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js51
-rw-r--r--layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js125
-rw-r--r--layout/base/tests/browser_stylesheet_change_events.js227
-rw-r--r--layout/base/tests/browser_visual_viewport_iframe.js59
-rw-r--r--layout/base/tests/bug1007065-1-ref.html15
-rw-r--r--layout/base/tests/bug1007065-1.html15
-rw-r--r--layout/base/tests/bug1007067-1-ref.html20
-rw-r--r--layout/base/tests/bug1007067-1.html20
-rw-r--r--layout/base/tests/bug1061468-ref.html13
-rw-r--r--layout/base/tests/bug1061468.html40
-rw-r--r--layout/base/tests/bug106855-1-ref.html27
-rw-r--r--layout/base/tests/bug106855-1.html25
-rw-r--r--layout/base/tests/bug106855-2.html26
-rw-r--r--layout/base/tests/bug1078327_inner.html107
-rw-r--r--layout/base/tests/bug1080360_inner.html83
-rw-r--r--layout/base/tests/bug1080361_inner.html114
-rw-r--r--layout/base/tests/bug1082486-1-ref.html18
-rw-r--r--layout/base/tests/bug1082486-1.html28
-rw-r--r--layout/base/tests/bug1082486-2-ref.html12
-rw-r--r--layout/base/tests/bug1082486-2.html12
-rw-r--r--layout/base/tests/bug1093686_inner.html83
-rw-r--r--layout/base/tests/bug1097242-1-ref.html14
-rw-r--r--layout/base/tests/bug1097242-1.html22
-rw-r--r--layout/base/tests/bug1109968-1-ref.html17
-rw-r--r--layout/base/tests/bug1109968-1.html23
-rw-r--r--layout/base/tests/bug1109968-2-ref.html17
-rw-r--r--layout/base/tests/bug1109968-2.html23
-rw-r--r--layout/base/tests/bug1123067-1.html38
-rw-r--r--layout/base/tests/bug1123067-2.html34
-rw-r--r--layout/base/tests/bug1123067-3.html35
-rw-r--r--layout/base/tests/bug1123067-ref.html33
-rw-r--r--layout/base/tests/bug1132768-1-ref.html12
-rw-r--r--layout/base/tests/bug1132768-1.html17
-rw-r--r--layout/base/tests/bug1153130_inner.html72
-rw-r--r--layout/base/tests/bug1162990_inner_1.html146
-rw-r--r--layout/base/tests/bug1162990_inner_2.html147
-rw-r--r--layout/base/tests/bug1226904.html35
-rw-r--r--layout/base/tests/bug1237236-1-ref.html30
-rw-r--r--layout/base/tests/bug1237236-1.html31
-rw-r--r--layout/base/tests/bug1237236-2-ref.html29
-rw-r--r--layout/base/tests/bug1237236-2.html30
-rw-r--r--layout/base/tests/bug1258308-1-ref.html34
-rw-r--r--layout/base/tests/bug1258308-1.html42
-rw-r--r--layout/base/tests/bug1258308-2-ref.html30
-rw-r--r--layout/base/tests/bug1258308-2.html31
-rw-r--r--layout/base/tests/bug1259949-1-ref.html30
-rw-r--r--layout/base/tests/bug1259949-1.html34
-rw-r--r--layout/base/tests/bug1259949-2-ref.html30
-rw-r--r--layout/base/tests/bug1259949-2.html30
-rw-r--r--layout/base/tests/bug1263288-ref.html28
-rw-r--r--layout/base/tests/bug1263288.html30
-rw-r--r--layout/base/tests/bug1263357-1-ref.html28
-rw-r--r--layout/base/tests/bug1263357-1.html34
-rw-r--r--layout/base/tests/bug1263357-2-ref.html28
-rw-r--r--layout/base/tests/bug1263357-2.html34
-rw-r--r--layout/base/tests/bug1263357-3-ref.html27
-rw-r--r--layout/base/tests/bug1263357-3.html28
-rw-r--r--layout/base/tests/bug1263357-4-ref.html27
-rw-r--r--layout/base/tests/bug1263357-4.html28
-rw-r--r--layout/base/tests/bug1263357-5-ref.html27
-rw-r--r--layout/base/tests/bug1263357-5.html28
-rw-r--r--layout/base/tests/bug1354478-1-ref.html35
-rw-r--r--layout/base/tests/bug1354478-1.html35
-rw-r--r--layout/base/tests/bug1354478-2-ref.html35
-rw-r--r--layout/base/tests/bug1354478-2.html35
-rw-r--r--layout/base/tests/bug1354478-3-ref.html38
-rw-r--r--layout/base/tests/bug1354478-3.html38
-rw-r--r--layout/base/tests/bug1354478-4-ref.html38
-rw-r--r--layout/base/tests/bug1354478-4.html38
-rw-r--r--layout/base/tests/bug1354478-5-ref.html38
-rw-r--r--layout/base/tests/bug1354478-5.html38
-rw-r--r--layout/base/tests/bug1354478-6-ref.html38
-rw-r--r--layout/base/tests/bug1354478-6.html38
-rw-r--r--layout/base/tests/bug1359411-ref.html11
-rw-r--r--layout/base/tests/bug1359411.html12
-rw-r--r--layout/base/tests/bug1415416-ref.html21
-rw-r--r--layout/base/tests/bug1415416.html28
-rw-r--r--layout/base/tests/bug1423331-1-ref.html23
-rw-r--r--layout/base/tests/bug1423331-1.html28
-rw-r--r--layout/base/tests/bug1423331-2-ref.html23
-rw-r--r--layout/base/tests/bug1423331-2.html29
-rw-r--r--layout/base/tests/bug1423331-3.html26
-rw-r--r--layout/base/tests/bug1423331-4.html27
-rw-r--r--layout/base/tests/bug1448730.html103
-rw-r--r--layout/base/tests/bug1484094-1-ref.html21
-rw-r--r--layout/base/tests/bug1484094-1.html23
-rw-r--r--layout/base/tests/bug1484094-2-ref.html21
-rw-r--r--layout/base/tests/bug1484094-2.html23
-rw-r--r--layout/base/tests/bug1496118-ref.html25
-rw-r--r--layout/base/tests/bug1496118.html37
-rw-r--r--layout/base/tests/bug1506547-1.html29
-rw-r--r--layout/base/tests/bug1506547-2.html30
-rw-r--r--layout/base/tests/bug1506547-3.html32
-rw-r--r--layout/base/tests/bug1506547-4-ref.html21
-rw-r--r--layout/base/tests/bug1506547-4.html21
-rw-r--r--layout/base/tests/bug1506547-5-ref.html26
-rw-r--r--layout/base/tests/bug1506547-5.html27
-rw-r--r--layout/base/tests/bug1506547-6.html27
-rw-r--r--layout/base/tests/bug1510942-1-ref.html20
-rw-r--r--layout/base/tests/bug1510942-1.html21
-rw-r--r--layout/base/tests/bug1510942-2-ref.html4
-rw-r--r--layout/base/tests/bug1510942-2.html21
-rw-r--r--layout/base/tests/bug1516963-1-ref.html16
-rw-r--r--layout/base/tests/bug1516963-1.html30
-rw-r--r--layout/base/tests/bug1516963-2-ref.html16
-rw-r--r--layout/base/tests/bug1516963-2.html30
-rw-r--r--layout/base/tests/bug1516963-3-ref.html20
-rw-r--r--layout/base/tests/bug1516963-3.html34
-rw-r--r--layout/base/tests/bug1516963-4-ref.html20
-rw-r--r--layout/base/tests/bug1516963-4.html34
-rw-r--r--layout/base/tests/bug1516963-5-ref.html16
-rw-r--r--layout/base/tests/bug1516963-5.html30
-rw-r--r--layout/base/tests/bug1516963-6-ref.html20
-rw-r--r--layout/base/tests/bug1516963-6.html34
-rw-r--r--layout/base/tests/bug1518339-1-ref.html16
-rw-r--r--layout/base/tests/bug1518339-1.html23
-rw-r--r--layout/base/tests/bug1518339-2-ref.html25
-rw-r--r--layout/base/tests/bug1518339-2.html23
-rw-r--r--layout/base/tests/bug1524266-1-ref.html30
-rw-r--r--layout/base/tests/bug1524266-1.html27
-rw-r--r--layout/base/tests/bug1524266-2-ref.html24
-rw-r--r--layout/base/tests/bug1524266-2.html38
-rw-r--r--layout/base/tests/bug1524266-3.html37
-rw-r--r--layout/base/tests/bug1524266-4.html30
-rw-r--r--layout/base/tests/bug1529492-1-ref.html30
-rw-r--r--layout/base/tests/bug1529492-1.html35
-rw-r--r--layout/base/tests/bug1550869-1-ref.html19
-rw-r--r--layout/base/tests/bug1550869-1a.html33
-rw-r--r--layout/base/tests/bug1550869-1b.html33
-rw-r--r--layout/base/tests/bug1550869-1c.html33
-rw-r--r--layout/base/tests/bug1550869-2-ref.html15
-rw-r--r--layout/base/tests/bug1550869-2a.html30
-rw-r--r--layout/base/tests/bug1550869-2b.html30
-rw-r--r--layout/base/tests/bug1550869-2c.html30
-rw-r--r--layout/base/tests/bug1550869-2d.html30
-rw-r--r--layout/base/tests/bug1591282-1-ref.html23
-rw-r--r--layout/base/tests/bug1591282-1.html27
-rw-r--r--layout/base/tests/bug1611661-ref.html18
-rw-r--r--layout/base/tests/bug1611661.html23
-rw-r--r--layout/base/tests/bug1634543-1-ref.html20
-rw-r--r--layout/base/tests/bug1634543-1.html25
-rw-r--r--layout/base/tests/bug1634543-2.html24
-rw-r--r--layout/base/tests/bug1634543-3.html20
-rw-r--r--layout/base/tests/bug1634543-4.html23
-rw-r--r--layout/base/tests/bug1634743-1-ref.html23
-rw-r--r--layout/base/tests/bug1634743-1.html29
-rw-r--r--layout/base/tests/bug1637476-1-ref.html24
-rw-r--r--layout/base/tests/bug1637476-1.html24
-rw-r--r--layout/base/tests/bug1637476-2-ref.html24
-rw-r--r--layout/base/tests/bug1637476-2.html24
-rw-r--r--layout/base/tests/bug1637476-3-ref.html24
-rw-r--r--layout/base/tests/bug1637476-3.html24
-rw-r--r--layout/base/tests/bug1663475-1-ref.html28
-rw-r--r--layout/base/tests/bug1663475-1.html32
-rw-r--r--layout/base/tests/bug1663475-2-ref.html29
-rw-r--r--layout/base/tests/bug1663475-2.html33
-rw-r--r--layout/base/tests/bug1670531-1.html27
-rw-r--r--layout/base/tests/bug1670531-2.html27
-rw-r--r--layout/base/tests/bug1670531-3-ref.html27
-rw-r--r--layout/base/tests/bug1670531-3.html27
-rw-r--r--layout/base/tests/bug1670531-4.html27
-rw-r--r--layout/base/tests/bug240933-1-ref.html12
-rw-r--r--layout/base/tests/bug240933-1.html13
-rw-r--r--layout/base/tests/bug240933-2.html15
-rw-r--r--layout/base/tests/bug369950-subframe.xml11
-rw-r--r--layout/base/tests/bug389321-1-ref.html17
-rw-r--r--layout/base/tests/bug389321-1.html19
-rw-r--r--layout/base/tests/bug389321-2-ref.html9
-rw-r--r--layout/base/tests/bug389321-2.html9
-rw-r--r--layout/base/tests/bug389321-3-ref.html9
-rw-r--r--layout/base/tests/bug389321-3.html9
-rw-r--r--layout/base/tests/bug450930.xhtml181
-rw-r--r--layout/base/tests/bug482484-ref.html18
-rw-r--r--layout/base/tests/bug482484.html22
-rw-r--r--layout/base/tests/bug503399-ref.html42
-rw-r--r--layout/base/tests/bug503399.html41
-rw-r--r--layout/base/tests/bug512295-1-ref.html30
-rw-r--r--layout/base/tests/bug512295-1.html36
-rw-r--r--layout/base/tests/bug512295-2-ref.html30
-rw-r--r--layout/base/tests/bug512295-2.html36
-rw-r--r--layout/base/tests/bug558663.html103
-rw-r--r--layout/base/tests/bug583889_inner1.html64
-rw-r--r--layout/base/tests/bug583889_inner2.html5
-rw-r--r--layout/base/tests/bug585922-ref.html21
-rw-r--r--layout/base/tests/bug585922.html35
-rw-r--r--layout/base/tests/bug597519-1-ref.html12
-rw-r--r--layout/base/tests/bug597519-1.html15
-rw-r--r--layout/base/tests/bug602141-1-ref.html18
-rw-r--r--layout/base/tests/bug602141-1.html21
-rw-r--r--layout/base/tests/bug602141-2-ref.html18
-rw-r--r--layout/base/tests/bug602141-2.html23
-rw-r--r--layout/base/tests/bug602141-3-ref.html18
-rw-r--r--layout/base/tests/bug602141-3.html21
-rw-r--r--layout/base/tests/bug602141-4-ref.html18
-rw-r--r--layout/base/tests/bug602141-4.html21
-rw-r--r--layout/base/tests/bug612271-1.html15
-rw-r--r--layout/base/tests/bug612271-2.html15
-rw-r--r--layout/base/tests/bug612271-3.html15
-rw-r--r--layout/base/tests/bug612271-ref.html17
-rw-r--r--layout/base/tests/bug613433-1.html24
-rw-r--r--layout/base/tests/bug613433-2.html24
-rw-r--r--layout/base/tests/bug613433-3.html24
-rw-r--r--layout/base/tests/bug613433-ref.html21
-rw-r--r--layout/base/tests/bug613807-1-ref.html6
-rw-r--r--layout/base/tests/bug613807-1.html90
-rw-r--r--layout/base/tests/bug632215-1.html29
-rw-r--r--layout/base/tests/bug632215-2.html28
-rw-r--r--layout/base/tests/bug632215-ref.html17
-rw-r--r--layout/base/tests/bug633044-1-ref.html16
-rw-r--r--layout/base/tests/bug633044-1.html24
-rw-r--r--layout/base/tests/bug634406-1-ref.html10
-rw-r--r--layout/base/tests/bug634406-1.html16
-rw-r--r--layout/base/tests/bug644428-1-ref.html17
-rw-r--r--layout/base/tests/bug644428-1.html19
-rw-r--r--layout/base/tests/bug646382-1-ref.html21
-rw-r--r--layout/base/tests/bug646382-1.html22
-rw-r--r--layout/base/tests/bug646382-2-ref.html18
-rw-r--r--layout/base/tests/bug646382-2.html21
-rw-r--r--layout/base/tests/bug664087-1-ref.html21
-rw-r--r--layout/base/tests/bug664087-1.html25
-rw-r--r--layout/base/tests/bug664087-2-ref.html21
-rw-r--r--layout/base/tests/bug664087-2.html25
-rw-r--r--layout/base/tests/bug682712-1-ref.html24
-rw-r--r--layout/base/tests/bug682712-1.html32
-rw-r--r--layout/base/tests/bug687297_a.html17
-rw-r--r--layout/base/tests/bug687297_b.html17
-rw-r--r--layout/base/tests/bug687297_c.html17
-rw-r--r--layout/base/tests/bug746993-1-ref.html20
-rw-r--r--layout/base/tests/bug746993-1.html22
-rw-r--r--layout/base/tests/bug851445_helper.html11
-rw-r--r--layout/base/tests/bug923376-ref.html12
-rw-r--r--layout/base/tests/bug923376.html16
-rw-r--r--layout/base/tests/bug956530-1-ref.html29
-rw-r--r--layout/base/tests/bug956530-1.html35
-rw-r--r--layout/base/tests/bug966992-1-ref.html40
-rw-r--r--layout/base/tests/bug966992-1.html36
-rw-r--r--layout/base/tests/bug966992-2-ref.html42
-rw-r--r--layout/base/tests/bug966992-2.html38
-rw-r--r--layout/base/tests/bug970964_inner.html357
-rw-r--r--layout/base/tests/bug970964_inner2.html356
-rw-r--r--layout/base/tests/bug977003_inner_1.html100
-rw-r--r--layout/base/tests/bug977003_inner_2.html75
-rw-r--r--layout/base/tests/bug977003_inner_3.html95
-rw-r--r--layout/base/tests/bug977003_inner_4.html100
-rw-r--r--layout/base/tests/bug977003_inner_5.html121
-rw-r--r--layout/base/tests/bug977003_inner_6.html105
-rw-r--r--layout/base/tests/bug989012-1-ref.html21
-rw-r--r--layout/base/tests/bug989012-1.html24
-rw-r--r--layout/base/tests/bug989012-2-ref.html26
-rw-r--r--layout/base/tests/bug989012-2.html29
-rw-r--r--layout/base/tests/bug989012-3-ref.html28
-rw-r--r--layout/base/tests/bug989012-3.html31
-rw-r--r--layout/base/tests/chrome/animated.gifbin0 -> 527 bytes
-rw-r--r--layout/base/tests/chrome/blue-32x32.pngbin0 -> 110 bytes
-rw-r--r--layout/base/tests/chrome/bug1041200_frame.html2
-rw-r--r--layout/base/tests/chrome/bug1041200_window.html40
-rw-r--r--layout/base/tests/chrome/bug1722890.html18
-rw-r--r--layout/base/tests/chrome/bug1722890_ref.html19
-rw-r--r--layout/base/tests/chrome/bug1769161_1.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_1_ref.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_2.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_2_ref.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_3.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_3_ref.html18
-rw-r--r--layout/base/tests/chrome/bug1769161_4.html17
-rw-r--r--layout/base/tests/chrome/bug1769161_4_ref.html18
-rw-r--r--layout/base/tests/chrome/bug495648.rdf214
-rw-r--r--layout/base/tests/chrome/bug551434_childframe.html4
-rw-r--r--layout/base/tests/chrome/chrome.toml167
-rw-r--r--layout/base/tests/chrome/chrome_content_integration_window.xhtml45
-rw-r--r--layout/base/tests/chrome/color_adjust.html9
-rw-r--r--layout/base/tests/chrome/color_adjust_ref.html8
-rw-r--r--layout/base/tests/chrome/default_background_window.xhtml67
-rw-r--r--layout/base/tests/chrome/dialog_with_positioning_window.xhtml30
-rw-r--r--layout/base/tests/chrome/file_bug1018265.xhtml50
-rw-r--r--layout/base/tests/chrome/file_bug458898.html1
-rw-r--r--layout/base/tests/chrome/file_bug465448.html1
-rw-r--r--layout/base/tests/chrome/frame_css_visibility_propagation.html1
-rw-r--r--layout/base/tests/chrome/green.pngbin0 -> 91 bytes
-rw-r--r--layout/base/tests/chrome/markA.ttfbin0 -> 1568 bytes
-rw-r--r--layout/base/tests/chrome/markB.ttfbin0 -> 1568 bytes
-rw-r--r--layout/base/tests/chrome/print_page_size1.html31
-rw-r--r--layout/base/tests/chrome/print_page_size1_ref.html31
-rw-r--r--layout/base/tests/chrome/print_page_size2.html31
-rw-r--r--layout/base/tests/chrome/print_page_size2_ref.html34
-rw-r--r--layout/base/tests/chrome/print_page_size3.html31
-rw-r--r--layout/base/tests/chrome/print_page_size3_ref.html33
-rw-r--r--layout/base/tests/chrome/print_page_size4.html31
-rw-r--r--layout/base/tests/chrome/print_page_size4_ref.html31
-rw-r--r--layout/base/tests/chrome/printpreview_bug1713404_ref.html27
-rw-r--r--layout/base/tests/chrome/printpreview_bug1730091_ref.html27
-rw-r--r--layout/base/tests/chrome/printpreview_bug396024_helper.xhtml96
-rw-r--r--layout/base/tests/chrome/printpreview_bug482976_helper.xhtml50
-rw-r--r--layout/base/tests/chrome/printpreview_downloadable_font.html24
-rw-r--r--layout/base/tests/chrome/printpreview_downloadable_font_in_iframe.html25
-rw-r--r--layout/base/tests/chrome/printpreview_downloadable_font_in_iframe_ref.html24
-rw-r--r--layout/base/tests/chrome/printpreview_downloadable_font_ref.html23
-rw-r--r--layout/base/tests/chrome/printpreview_font_api.html25
-rw-r--r--layout/base/tests/chrome/printpreview_font_api_ref.html23
-rw-r--r--layout/base/tests/chrome/printpreview_font_mozprintcallback.html27
-rw-r--r--layout/base/tests/chrome/printpreview_font_mozprintcallback_ref.html28
-rw-r--r--layout/base/tests/chrome/printpreview_helper.xhtml1721
-rw-r--r--layout/base/tests/chrome/printpreview_image_select.html7
-rw-r--r--layout/base/tests/chrome/printpreview_image_select_ref.html4
-rw-r--r--layout/base/tests/chrome/printpreview_images.html25
-rw-r--r--layout/base/tests/chrome/printpreview_images_ref.html15
-rw-r--r--layout/base/tests/chrome/printpreview_images_sw.html46
-rw-r--r--layout/base/tests/chrome/printpreview_images_sw.js11
-rw-r--r--layout/base/tests/chrome/printpreview_images_sw_ref.html14
-rw-r--r--layout/base/tests/chrome/printpreview_mask.html16
-rw-r--r--layout/base/tests/chrome/printpreview_mixed_page_size_001.html11
-rw-r--r--layout/base/tests/chrome/printpreview_mixed_page_size_002.html15
-rw-r--r--layout/base/tests/chrome/printpreview_pps16.html43
-rw-r--r--layout/base/tests/chrome/printpreview_pps16_ref.html49
-rw-r--r--layout/base/tests/chrome/printpreview_pps2.html15
-rw-r--r--layout/base/tests/chrome/printpreview_pps2_ref.html40
-rw-r--r--layout/base/tests/chrome/printpreview_pps4.html19
-rw-r--r--layout/base/tests/chrome/printpreview_pps4_ref.html25
-rw-r--r--layout/base/tests/chrome/printpreview_pps6.html40
-rw-r--r--layout/base/tests/chrome/printpreview_pps6_ref.html90
-rw-r--r--layout/base/tests/chrome/printpreview_pps9.html29
-rw-r--r--layout/base/tests/chrome/printpreview_pps9_ref.html40
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw2.html16
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw2_no_margin_ref.html36
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw2_ref.html49
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw4.html19
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw4_ref.html70
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw9.html29
-rw-r--r--layout/base/tests/chrome/printpreview_pps_uw9_ref.html82
-rw-r--r--layout/base/tests/chrome/printpreview_prettyprint.xml1
-rw-r--r--layout/base/tests/chrome/printpreview_prettyprint_ref.xhtml3
-rw-r--r--layout/base/tests/chrome/printpreview_quirks.html8
-rw-r--r--layout/base/tests/chrome/printpreview_quirks_ref.html5
-rw-r--r--layout/base/tests/chrome/red.pngbin0 -> 510 bytes
-rw-r--r--layout/base/tests/chrome/test_bug1018265.xhtml37
-rw-r--r--layout/base/tests/chrome/test_bug1041200.xhtml22
-rw-r--r--layout/base/tests/chrome/test_bug396367-1.html40
-rw-r--r--layout/base/tests/chrome/test_bug396367-2.html47
-rw-r--r--layout/base/tests/chrome/test_bug420499.xhtml126
-rw-r--r--layout/base/tests/chrome/test_bug458898.html39
-rw-r--r--layout/base/tests/chrome/test_bug465448.xhtml45
-rw-r--r--layout/base/tests/chrome/test_bug514660.xhtml35
-rw-r--r--layout/base/tests/chrome/test_bug533845.xhtml49
-rw-r--r--layout/base/tests/chrome/test_bug551434.html95
-rw-r--r--layout/base/tests/chrome/test_bug708062.html43
-rw-r--r--layout/base/tests/chrome/test_bug812817.xhtml37
-rw-r--r--layout/base/tests/chrome/test_chrome_content_integration.xhtml24
-rw-r--r--layout/base/tests/chrome/test_color_scheme_browser.xhtml145
-rw-r--r--layout/base/tests/chrome/test_css_visibility_propagation.xhtml209
-rw-r--r--layout/base/tests/chrome/test_default_background.xhtml22
-rw-r--r--layout/base/tests/chrome/test_dialog_with_positioning.html20
-rw-r--r--layout/base/tests/chrome/test_document_adopted_styles.html8
-rw-r--r--layout/base/tests/chrome/test_document_adopted_styles_ref.html6
-rw-r--r--layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html40
-rw-r--r--layout/base/tests/chrome/test_getClientRectsAndTexts.html80
-rw-r--r--layout/base/tests/chrome/test_get_printer_basic_attributes.html36
-rw-r--r--layout/base/tests/chrome/test_get_printer_orientation.html50
-rw-r--r--layout/base/tests/chrome/test_get_printer_paper_sizes.html69
-rw-r--r--layout/base/tests/chrome/test_prerendered_transforms.html46
-rw-r--r--layout/base/tests/chrome/test_printer_default_settings.html63
-rw-r--r--layout/base/tests/chrome/test_printpreview.xhtml16
-rw-r--r--layout/base/tests/chrome/test_printpreview_bug396024.xhtml21
-rw-r--r--layout/base/tests/chrome/test_printpreview_bug482976.xhtml21
-rw-r--r--layout/base/tests/chrome/test_scrolling_repaints.html48
-rw-r--r--layout/base/tests/chrome/test_shadow_root_adopted_styles.html11
-rw-r--r--layout/base/tests/chrome/test_shadow_root_adopted_styles_ref.html11
-rw-r--r--layout/base/tests/chrome/test_shared_adopted_styles.html19
-rw-r--r--layout/base/tests/chrome/test_shared_adopted_styles_ref.html16
-rw-r--r--layout/base/tests/chrome/test_will_change.html140
-rw-r--r--layout/base/tests/chrome/window_css_visibility_propagation-1.xhtml6
-rw-r--r--layout/base/tests/chrome/window_css_visibility_propagation-2.xhtml6
-rw-r--r--layout/base/tests/chrome/window_css_visibility_propagation-3.html3
-rw-r--r--layout/base/tests/chrome/window_css_visibility_propagation-4.html3
-rw-r--r--layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input-ref.html27
-rw-r--r--layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input.html33
-rw-r--r--layout/base/tests/file_bug607529-1.html12
-rw-r--r--layout/base/tests/file_bug607529.html51
-rw-r--r--layout/base/tests/file_bug842853-frame.html6
-rw-r--r--layout/base/tests/file_bug842853.html17
-rw-r--r--layout/base/tests/file_bug842853.sjs16
-rw-r--r--layout/base/tests/file_dynamic_toolbar_max_height.html56
-rw-r--r--layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame1.html3
-rw-r--r--layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame2.html1
-rw-r--r--layout/base/tests/file_lazyload_telemetry.html9
-rw-r--r--layout/base/tests/file_stylesheet_change_events.html10
-rw-r--r--layout/base/tests/file_synthmousemove.html49
-rw-r--r--layout/base/tests/file_zoom_restore_bfcache.html92
-rw-r--r--layout/base/tests/helper_bug1701027-1.html10
-rw-r--r--layout/base/tests/helper_bug1701027-2.html10
-rw-r--r--layout/base/tests/helper_synthmousemove.html3
-rw-r--r--layout/base/tests/image_rgrg-256x256.pngbin0 -> 131 bytes
-rw-r--r--layout/base/tests/input-invalid-ref.html7
-rw-r--r--layout/base/tests/input-maxlength-invalid-change.html25
-rw-r--r--layout/base/tests/input-maxlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/input-maxlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/input-maxlength-valid-before-change.html15
-rw-r--r--layout/base/tests/input-maxlength-valid-change.html28
-rw-r--r--layout/base/tests/input-minlength-invalid-change.html25
-rw-r--r--layout/base/tests/input-minlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/input-minlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/input-minlength-valid-before-change.html15
-rw-r--r--layout/base/tests/input-minlength-valid-change.html28
-rw-r--r--layout/base/tests/input-password-RTL-input-ref.html23
-rw-r--r--layout/base/tests/input-password-RTL-input.html31
-rw-r--r--layout/base/tests/input-password-remask-ref.html20
-rw-r--r--layout/base/tests/input-password-remask.html23
-rw-r--r--layout/base/tests/input-password-unmask-around-emoji-ref.html40
-rw-r--r--layout/base/tests/input-password-unmask-around-emoji.html21
-rw-r--r--layout/base/tests/input-password-unmask-ref.html33
-rw-r--r--layout/base/tests/input-password-unmask.html25
-rw-r--r--layout/base/tests/input-stoppropagation-ref.html16
-rw-r--r--layout/base/tests/input-stoppropagation.html20
-rw-r--r--layout/base/tests/input-ui-valid-ref.html6
-rw-r--r--layout/base/tests/input-valid-ref.html7
-rw-r--r--layout/base/tests/interlinePosition-after-Selection-addRange-ref.html20
-rw-r--r--layout/base/tests/interlinePosition-after-Selection-addRange.html21
-rw-r--r--layout/base/tests/marionette/manifest.toml7
-rw-r--r--layout/base/tests/marionette/selection.py363
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py280
-rw-r--r--layout/base/tests/marionette/test_accessiblecaret_selection_mode.py805
-rw-r--r--layout/base/tests/mochitest.toml594
-rw-r--r--layout/base/tests/multi-range-script-select-ref.html173
-rw-r--r--layout/base/tests/multi-range-script-select.html185
-rw-r--r--layout/base/tests/multi-range-user-select-ref.html166
-rw-r--r--layout/base/tests/multi-range-user-select.html223
-rw-r--r--layout/base/tests/partial.pngbin0 -> 4000 bytes
-rw-r--r--layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html89
-rw-r--r--layout/base/tests/preserve3d_sorting_hit_testing_iframe.html28
-rw-r--r--layout/base/tests/resize_flush_iframe.html17
-rw-r--r--layout/base/tests/scroll_into_view_in_child.html8
-rw-r--r--layout/base/tests/scroll_selection_into_view_window.html59
-rw-r--r--layout/base/tests/scroll_selection_into_view_window_frame.html6
-rw-r--r--layout/base/tests/selection-utils.js167
-rw-r--r--layout/base/tests/sendimagenevercomplete.sjs29
-rw-r--r--layout/base/tests/stylesheet_change_events.css1
-rw-r--r--layout/base/tests/test_accessiblecaret_magnifier.html33
-rw-r--r--layout/base/tests/test_after_paint_pref.html123
-rw-r--r--layout/base/tests/test_border_radius_hit_testing.html106
-rw-r--r--layout/base/tests/test_bug1078327.html30
-rw-r--r--layout/base/tests/test_bug1080360.html30
-rw-r--r--layout/base/tests/test_bug1080361.html31
-rw-r--r--layout/base/tests/test_bug1093686.html41
-rw-r--r--layout/base/tests/test_bug1120705.html98
-rw-r--r--layout/base/tests/test_bug114649.html78
-rw-r--r--layout/base/tests/test_bug1153130.html30
-rw-r--r--layout/base/tests/test_bug1162990.html32
-rw-r--r--layout/base/tests/test_bug1216483.html204
-rw-r--r--layout/base/tests/test_bug1226904.html44
-rw-r--r--layout/base/tests/test_bug1246622.html45
-rw-r--r--layout/base/tests/test_bug1278021.html45
-rw-r--r--layout/base/tests/test_bug1448730.html30
-rw-r--r--layout/base/tests/test_bug1515822.html47
-rw-r--r--layout/base/tests/test_bug1550869_video.html35
-rw-r--r--layout/base/tests/test_bug1714640.html36
-rw-r--r--layout/base/tests/test_bug1756118.html87
-rw-r--r--layout/base/tests/test_bug332655-1.html57
-rw-r--r--layout/base/tests/test_bug332655-2.html67
-rw-r--r--layout/base/tests/test_bug369950.html91
-rw-r--r--layout/base/tests/test_bug370436.html91
-rw-r--r--layout/base/tests/test_bug386575.xhtml46
-rw-r--r--layout/base/tests/test_bug388019.html44
-rw-r--r--layout/base/tests/test_bug394057.html88
-rw-r--r--layout/base/tests/test_bug399284.html115
-rw-r--r--layout/base/tests/test_bug399951.html34
-rw-r--r--layout/base/tests/test_bug404209.xhtml47
-rw-r--r--layout/base/tests/test_bug416896.html64
-rw-r--r--layout/base/tests/test_bug423523.html104
-rw-r--r--layout/base/tests/test_bug435293-interaction.html49
-rw-r--r--layout/base/tests/test_bug435293-scale.html103
-rw-r--r--layout/base/tests/test_bug435293-skew.html173
-rw-r--r--layout/base/tests/test_bug449781.html62
-rw-r--r--layout/base/tests/test_bug450930.xhtml28
-rw-r--r--layout/base/tests/test_bug469170.html48
-rw-r--r--layout/base/tests/test_bug471126.html34
-rw-r--r--layout/base/tests/test_bug499538-1.html53
-rw-r--r--layout/base/tests/test_bug514127.html55
-rw-r--r--layout/base/tests/test_bug518777.html44
-rw-r--r--layout/base/tests/test_bug548545.xhtml47
-rw-r--r--layout/base/tests/test_bug558663.html37
-rw-r--r--layout/base/tests/test_bug559499.html26
-rw-r--r--layout/base/tests/test_bug569520.html67
-rw-r--r--layout/base/tests/test_bug582181-1.html60
-rw-r--r--layout/base/tests/test_bug582181-2.html63
-rw-r--r--layout/base/tests/test_bug582771.html128
-rw-r--r--layout/base/tests/test_bug583889.html53
-rw-r--r--layout/base/tests/test_bug588174.html67
-rw-r--r--layout/base/tests/test_bug603550.html118
-rw-r--r--layout/base/tests/test_bug607529.html119
-rw-r--r--layout/base/tests/test_bug629838.html87
-rw-r--r--layout/base/tests/test_bug644768.html62
-rw-r--r--layout/base/tests/test_bug646757.html43
-rw-r--r--layout/base/tests/test_bug66619.html72
-rw-r--r--layout/base/tests/test_bug667512.html40
-rw-r--r--layout/base/tests/test_bug677878.html54
-rw-r--r--layout/base/tests/test_bug687297.html54
-rw-r--r--layout/base/tests/test_bug696020.html47
-rw-r--r--layout/base/tests/test_bug718809.html28
-rw-r--r--layout/base/tests/test_bug725426.html23
-rw-r--r--layout/base/tests/test_bug731777.html49
-rw-r--r--layout/base/tests/test_bug749186.html40
-rw-r--r--layout/base/tests/test_bug761572.html40
-rw-r--r--layout/base/tests/test_bug770106.html24
-rw-r--r--layout/base/tests/test_bug842853-2.html56
-rw-r--r--layout/base/tests/test_bug842853.html52
-rw-r--r--layout/base/tests/test_bug849219.html50
-rw-r--r--layout/base/tests/test_bug851445.html34
-rw-r--r--layout/base/tests/test_bug851485.html86
-rw-r--r--layout/base/tests/test_bug858459.html59
-rw-r--r--layout/base/tests/test_bug93077-1.html31
-rw-r--r--layout/base/tests/test_bug93077-2.html31
-rw-r--r--layout/base/tests/test_bug93077-3.html34
-rw-r--r--layout/base/tests/test_bug93077-4.html34
-rw-r--r--layout/base/tests/test_bug93077-5.html34
-rw-r--r--layout/base/tests/test_bug93077-6.html34
-rw-r--r--layout/base/tests/test_bug970964.html46
-rw-r--r--layout/base/tests/test_bug977003.html32
-rw-r--r--layout/base/tests/test_bug990340.html60
-rw-r--r--layout/base/tests/test_bug993936.html164
-rw-r--r--layout/base/tests/test_caret_browsing_around_form_controls.html379
-rw-r--r--layout/base/tests/test_dynamic_toolbar_max_height.html22
-rw-r--r--layout/base/tests/test_emulateMedium.html165
-rw-r--r--layout/base/tests/test_emulate_color_scheme.html40
-rw-r--r--layout/base/tests/test_event_target_iframe_oop.html177
-rw-r--r--layout/base/tests/test_event_target_radius.html422
-rw-r--r--layout/base/tests/test_frame_reconstruction_body_table.html48
-rw-r--r--layout/base/tests/test_frame_reconstruction_body_writing_mode.html34
-rw-r--r--layout/base/tests/test_frame_reconstruction_for_column_span.html77
-rw-r--r--layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html74
-rw-r--r--layout/base/tests/test_frame_reconstruction_for_svg_transforms.html46
-rw-r--r--layout/base/tests/test_frame_reconstruction_scroll_restore.html82
-rw-r--r--layout/base/tests/test_getBoxQuads_convertPointRectQuad.html717
-rw-r--r--layout/base/tests/test_getClientRects_emptytext.html26
-rw-r--r--layout/base/tests/test_mozPaintCount.html62
-rw-r--r--layout/base/tests/test_partialbg.html76
-rw-r--r--layout/base/tests/test_preserve3d_sorting_hit_testing.html48
-rw-r--r--layout/base/tests/test_preserve3d_sorting_hit_testing2.html40
-rw-r--r--layout/base/tests/test_refreshDriver_hasPendingTick.html95
-rw-r--r--layout/base/tests/test_reftests_with_caret.html465
-rw-r--r--layout/base/tests/test_resize_flush.html51
-rw-r--r--layout/base/tests/test_scroll_event_ordering.html63
-rw-r--r--layout/base/tests/test_scroll_into_view_in_oopif.html17
-rw-r--r--layout/base/tests/test_scroll_selection_into_view.html97
-rw-r--r--layout/base/tests/test_scroll_space_no_range_overflow_scroll.html67
-rw-r--r--layout/base/tests/test_synthmousemove.html31
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints.html54
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints_2.html54
-rw-r--r--layout/base/tests/test_transformed_scrolling_repaints_3.html24
-rw-r--r--layout/base/tests/test_visual_viewport_in_oopif.html11
-rw-r--r--layout/base/tests/test_zoom_restore_bfcache.html127
-rw-r--r--layout/base/tests/textarea-invalid-ref.html7
-rw-r--r--layout/base/tests/textarea-maxlength-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-maxlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-maxlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/textarea-maxlength-valid-before-change.html15
-rw-r--r--layout/base/tests/textarea-maxlength-valid-change.html28
-rw-r--r--layout/base/tests/textarea-minlength-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-minlength-ui-invalid-change.html25
-rw-r--r--layout/base/tests/textarea-minlength-ui-valid-change.html28
-rw-r--r--layout/base/tests/textarea-minlength-valid-before-change.html15
-rw-r--r--layout/base/tests/textarea-minlength-valid-change.html28
-rw-r--r--layout/base/tests/textarea-valid-ref.html7
-rw-r--r--layout/base/tests/transformed_scrolling_repaints_3_window.html46
-rw-r--r--layout/base/tests/transformed_scrolling_repaints_3_window_frame.html58
-rw-r--r--layout/base/tests/visual_viewport_in_child.html25
1297 files changed, 150106 insertions, 0 deletions
diff --git a/layout/base/AccessibleCaret.cpp b/layout/base/AccessibleCaret.cpp
new file mode 100644
index 0000000000..4981f0b703
--- /dev/null
+++ b/layout/base/AccessibleCaret.cpp
@@ -0,0 +1,378 @@
+/* -*- 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 "AccessibleCaret.h"
+
+#include "AccessibleCaretLogger.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/ToString.h"
+#include "nsCanvasFrame.h"
+#include "nsCaret.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDOMTokenList.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+
+namespace mozilla {
+using namespace dom;
+
+#undef AC_LOG
+#define AC_LOG(message, ...) \
+ AC_LOG_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
+
+#undef AC_LOGV
+#define AC_LOGV(message, ...) \
+ AC_LOGV_BASE("AccessibleCaret (%p): " message, this, ##__VA_ARGS__);
+
+NS_IMPL_ISUPPORTS(AccessibleCaret::DummyTouchListener, nsIDOMEventListener)
+
+static constexpr auto kTextOverlayElementId = u"text-overlay"_ns;
+static constexpr auto kCaretImageElementId = u"image"_ns;
+
+#define AC_PROCESS_ENUM_TO_STREAM(e) \
+ case (e): \
+ aStream << #e; \
+ break;
+std::ostream& operator<<(std::ostream& aStream,
+ const AccessibleCaret::Appearance& aAppearance) {
+ using Appearance = AccessibleCaret::Appearance;
+ switch (aAppearance) {
+ AC_PROCESS_ENUM_TO_STREAM(Appearance::None);
+ AC_PROCESS_ENUM_TO_STREAM(Appearance::Normal);
+ AC_PROCESS_ENUM_TO_STREAM(Appearance::NormalNotShown);
+ AC_PROCESS_ENUM_TO_STREAM(Appearance::Left);
+ AC_PROCESS_ENUM_TO_STREAM(Appearance::Right);
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(
+ std::ostream& aStream,
+ const AccessibleCaret::PositionChangedResult& aResult) {
+ using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+ switch (aResult) {
+ AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::NotChanged);
+ AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Position);
+ AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Zoom);
+ AC_PROCESS_ENUM_TO_STREAM(PositionChangedResult::Invisible);
+ }
+ return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
+// -----------------------------------------------------------------------------
+// Implementation of AccessibleCaret methods
+
+AccessibleCaret::AccessibleCaret(PresShell* aPresShell)
+ : mPresShell(aPresShell) {
+ // Check all resources required.
+ if (mPresShell) {
+ MOZ_ASSERT(mPresShell->GetDocument());
+ InjectCaretElement(mPresShell->GetDocument());
+ }
+}
+
+AccessibleCaret::~AccessibleCaret() {
+ if (mPresShell) {
+ RemoveCaretElement(mPresShell->GetDocument());
+ }
+}
+
+dom::Element* AccessibleCaret::TextOverlayElement() const {
+ return mCaretElementHolder->Root()->GetElementById(kTextOverlayElementId);
+}
+
+dom::Element* AccessibleCaret::CaretImageElement() const {
+ return mCaretElementHolder->Root()->GetElementById(kCaretImageElementId);
+}
+
+void AccessibleCaret::SetAppearance(Appearance aAppearance) {
+ if (mAppearance == aAppearance) {
+ return;
+ }
+
+ IgnoredErrorResult rv;
+ CaretElement().ClassList()->Remove(AppearanceString(mAppearance), rv);
+ MOZ_ASSERT(!rv.Failed(), "Remove old appearance failed!");
+
+ CaretElement().ClassList()->Add(AppearanceString(aAppearance), rv);
+ MOZ_ASSERT(!rv.Failed(), "Add new appearance failed!");
+
+ AC_LOG("%s: %s -> %s", __FUNCTION__, ToString(mAppearance).c_str(),
+ ToString(aAppearance).c_str());
+
+ mAppearance = aAppearance;
+
+ // Need to reset rect since the cached rect will be compared in SetPosition.
+ if (mAppearance == Appearance::None) {
+ ClearCachedData();
+ }
+}
+
+/* static */
+nsAutoString AccessibleCaret::AppearanceString(Appearance aAppearance) {
+ nsAutoString string;
+ switch (aAppearance) {
+ case Appearance::None:
+ string = u"none"_ns;
+ break;
+ case Appearance::NormalNotShown:
+ string = u"hidden"_ns;
+ break;
+ case Appearance::Normal:
+ string = u"normal"_ns;
+ break;
+ case Appearance::Right:
+ string = u"right"_ns;
+ break;
+ case Appearance::Left:
+ string = u"left"_ns;
+ break;
+ }
+ return string;
+}
+
+bool AccessibleCaret::Intersects(const AccessibleCaret& aCaret) const {
+ MOZ_ASSERT(mPresShell == aCaret.mPresShell);
+
+ if (!IsVisuallyVisible() || !aCaret.IsVisuallyVisible()) {
+ return false;
+ }
+
+ nsRect rect =
+ nsLayoutUtils::GetRectRelativeToFrame(&CaretElement(), RootFrame());
+ nsRect rhsRect = nsLayoutUtils::GetRectRelativeToFrame(&aCaret.CaretElement(),
+ RootFrame());
+ return rect.Intersects(rhsRect);
+}
+
+bool AccessibleCaret::Contains(const nsPoint& aPoint,
+ TouchArea aTouchArea) const {
+ if (!IsVisuallyVisible()) {
+ return false;
+ }
+
+ nsRect textOverlayRect =
+ nsLayoutUtils::GetRectRelativeToFrame(TextOverlayElement(), RootFrame());
+ nsRect caretImageRect =
+ nsLayoutUtils::GetRectRelativeToFrame(CaretImageElement(), RootFrame());
+
+ if (aTouchArea == TouchArea::CaretImage) {
+ return caretImageRect.Contains(aPoint);
+ }
+
+ MOZ_ASSERT(aTouchArea == TouchArea::Full, "Unexpected TouchArea type!");
+ return textOverlayRect.Contains(aPoint) || caretImageRect.Contains(aPoint);
+}
+
+void AccessibleCaret::EnsureApzAware() {
+ // If the caret element was cloned, the listener might have been lost. So
+ // if that's the case we register a dummy listener if there isn't one on
+ // the element already.
+ if (!CaretElement().IsApzAware()) {
+ CaretElement().AddEventListener(u"touchstart"_ns, mDummyTouchListener,
+ false);
+ }
+}
+
+bool AccessibleCaret::IsInPositionFixedSubtree() const {
+ return nsLayoutUtils::IsInPositionFixedSubtree(
+ mImaginaryCaretReferenceFrame.GetFrame());
+}
+
+void AccessibleCaret::InjectCaretElement(Document* aDocument) {
+ mCaretElementHolder =
+ aDocument->InsertAnonymousContent(/* aForce = */ false, IgnoreErrors());
+ MOZ_RELEASE_ASSERT(mCaretElementHolder, "We must have anonymous content!");
+
+ CreateCaretElement();
+ EnsureApzAware();
+}
+
+void AccessibleCaret::CreateCaretElement() const {
+ // Content structure of AccessibleCaret
+ // <div class="moz-accessiblecaret"> <- CaretElement()
+ // <#shadow-root>
+ // <link rel="stylesheet" href="accessiblecaret.css">
+ // <div id="text-overlay"> <- TextOverlayElement()
+ // <div id="image"> <- CaretImageElement()
+
+ constexpr bool kNotify = false;
+
+ Element& host = CaretElement();
+ host.SetAttr(kNameSpaceID_None, nsGkAtoms::_class,
+ u"moz-accessiblecaret none"_ns, kNotify);
+
+ ShadowRoot* root = mCaretElementHolder->Root();
+ Document* doc = host.OwnerDoc();
+ {
+ RefPtr<NodeInfo> linkNodeInfo = doc->NodeInfoManager()->GetNodeInfo(
+ nsGkAtoms::link, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
+ RefPtr<nsGenericHTMLElement> link =
+ NS_NewHTMLLinkElement(linkNodeInfo.forget());
+ if (NS_WARN_IF(!link)) {
+ return;
+ }
+ link->SetAttr(nsGkAtoms::rel, u"stylesheet"_ns, IgnoreErrors());
+ link->SetAttr(nsGkAtoms::href,
+ u"resource://content-accessible/accessiblecaret.css"_ns,
+ IgnoreErrors());
+ root->AppendChildTo(link, kNotify, IgnoreErrors());
+ }
+
+ auto CreateAndAppendChildElement = [&](const nsLiteralString& aElementId) {
+ RefPtr<Element> child = doc->CreateHTMLElement(nsGkAtoms::div);
+ child->SetAttr(kNameSpaceID_None, nsGkAtoms::id, aElementId, kNotify);
+ mCaretElementHolder->Root()->AppendChildTo(child, kNotify, IgnoreErrors());
+ };
+
+ CreateAndAppendChildElement(kTextOverlayElementId);
+ CreateAndAppendChildElement(kCaretImageElementId);
+}
+
+void AccessibleCaret::RemoveCaretElement(Document* aDocument) {
+ CaretElement().RemoveEventListener(u"touchstart"_ns, mDummyTouchListener,
+ false);
+
+ aDocument->RemoveAnonymousContent(*mCaretElementHolder);
+}
+
+void AccessibleCaret::ClearCachedData() {
+ mImaginaryCaretRect = nsRect();
+ mImaginaryCaretRectInContainerFrame = nsRect();
+ mImaginaryCaretReferenceFrame = nullptr;
+ mZoomLevel = 0.0f;
+}
+
+AccessibleCaret::PositionChangedResult AccessibleCaret::SetPosition(
+ nsIFrame* aFrame, int32_t aOffset) {
+ if (!CustomContentContainerFrame()) {
+ return PositionChangedResult::NotChanged;
+ }
+
+ nsRect imaginaryCaretRectInFrame =
+ nsCaret::GetGeometryForFrame(aFrame, aOffset, nullptr);
+
+ imaginaryCaretRectInFrame =
+ nsLayoutUtils::ClampRectToScrollFrames(aFrame, imaginaryCaretRectInFrame);
+
+ if (imaginaryCaretRectInFrame.IsEmpty()) {
+ // Don't bother to set the caret position since it's invisible.
+ ClearCachedData();
+ return PositionChangedResult::Invisible;
+ }
+
+ // SetCaretElementStyle() requires the input rect relative to the custom
+ // content container frame.
+ nsRect imaginaryCaretRectInContainerFrame = imaginaryCaretRectInFrame;
+ nsLayoutUtils::TransformRect(aFrame, CustomContentContainerFrame(),
+ imaginaryCaretRectInContainerFrame);
+ const float zoomLevel = GetZoomLevel();
+ const bool isSamePosition = imaginaryCaretRectInContainerFrame.IsEqualEdges(
+ mImaginaryCaretRectInContainerFrame);
+ const bool isSameZoomLevel = FuzzyEqualsMultiplicative(zoomLevel, mZoomLevel);
+
+ // Always update cached mImaginaryCaretRect (relative to the root frame)
+ // because it can change when the caret is scrolled.
+ mImaginaryCaretRect = imaginaryCaretRectInFrame;
+ nsLayoutUtils::TransformRect(aFrame, RootFrame(), mImaginaryCaretRect);
+
+ if (isSamePosition && isSameZoomLevel) {
+ return PositionChangedResult::NotChanged;
+ }
+
+ mImaginaryCaretRectInContainerFrame = imaginaryCaretRectInContainerFrame;
+ mImaginaryCaretReferenceFrame = aFrame;
+ mZoomLevel = zoomLevel;
+
+ SetCaretElementStyle(imaginaryCaretRectInContainerFrame, mZoomLevel);
+
+ return isSamePosition ? PositionChangedResult::Zoom
+ : PositionChangedResult::Position;
+}
+
+nsIFrame* AccessibleCaret::RootFrame() const {
+ return mPresShell->GetRootFrame();
+}
+
+nsIFrame* AccessibleCaret::CustomContentContainerFrame() const {
+ nsCanvasFrame* canvasFrame = mPresShell->GetCanvasFrame();
+ Element* container = canvasFrame->GetCustomContentContainer();
+ nsIFrame* containerFrame = container->GetPrimaryFrame();
+ return containerFrame;
+}
+
+void AccessibleCaret::SetCaretElementStyle(const nsRect& aRect,
+ float aZoomLevel) {
+ nsPoint position = CaretElementPosition(aRect);
+ nsAutoString styleStr;
+ // We can't use AppendPrintf here, because it does locale-specific
+ // formatting of floating-point values.
+ styleStr.AppendLiteral("left: ");
+ styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(position.x));
+ styleStr.AppendLiteral("px; top: ");
+ styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(position.y));
+ styleStr.AppendLiteral("px; width: ");
+ styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_width() /
+ aZoomLevel);
+ styleStr.AppendLiteral("px; margin-left: ");
+ styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_margin_left() /
+ aZoomLevel);
+ styleStr.AppendLiteral("px; transition-duration: ");
+ styleStr.AppendFloat(
+ StaticPrefs::layout_accessiblecaret_transition_duration());
+ styleStr.AppendLiteral("ms");
+
+ CaretElement().SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr, true);
+ AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
+
+ // Set style string for children.
+ SetTextOverlayElementStyle(aRect, aZoomLevel);
+ SetCaretImageElementStyle(aRect, aZoomLevel);
+}
+
+void AccessibleCaret::SetTextOverlayElementStyle(const nsRect& aRect,
+ float aZoomLevel) {
+ nsAutoString styleStr;
+ styleStr.AppendLiteral("height: ");
+ styleStr.AppendFloat(nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
+ styleStr.AppendLiteral("px;");
+ TextOverlayElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
+ true);
+ AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
+}
+
+void AccessibleCaret::SetCaretImageElementStyle(const nsRect& aRect,
+ float aZoomLevel) {
+ nsAutoString styleStr;
+ styleStr.AppendLiteral("height: ");
+ styleStr.AppendFloat(StaticPrefs::layout_accessiblecaret_height() /
+ aZoomLevel);
+ styleStr.AppendLiteral("px;");
+ CaretImageElement()->SetAttr(kNameSpaceID_None, nsGkAtoms::style, styleStr,
+ true);
+ AC_LOG("%s: %s", __FUNCTION__, NS_ConvertUTF16toUTF8(styleStr).get());
+}
+
+float AccessibleCaret::GetZoomLevel() {
+ // Full zoom on desktop.
+ float fullZoom = mPresShell->GetPresContext()->GetFullZoom();
+
+ // Pinch-zoom on fennec.
+ float resolution = mPresShell->GetCumulativeResolution();
+
+ return fullZoom * resolution;
+}
+
+} // namespace mozilla
diff --git a/layout/base/AccessibleCaret.h b/layout/base/AccessibleCaret.h
new file mode 100644
index 0000000000..120cfc47fa
--- /dev/null
+++ b/layout/base/AccessibleCaret.h
@@ -0,0 +1,230 @@
+/* -*- 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 AccessibleCaret_h__
+#define AccessibleCaret_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsIFrame.h" // for WeakFrame only
+#include "nsISupports.h"
+#include "nsISupportsImpl.h"
+#include "nsLiteralString.h"
+#include "nsRect.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+
+class nsIFrame;
+struct nsPoint;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Element;
+class Event;
+} // namespace dom
+
+// -----------------------------------------------------------------------------
+// Upon the creation of AccessibleCaret, it will insert DOM Element as an
+// anonymous content containing the caret image. The caret appearance and
+// position can be controlled by SetAppearance() and SetPosition().
+//
+// All the rect or point are relative to root frame except being specified
+// explicitly.
+//
+// None of the methods in AccessibleCaret will flush layout or style. To ensure
+// that SetPosition() works correctly, the caller must make sure the layout is
+// up to date.
+//
+// Please see the wiki page for more information.
+// https://wiki.mozilla.org/AccessibleCaret
+//
+class AccessibleCaret {
+ public:
+ explicit AccessibleCaret(PresShell* aPresShell);
+ virtual ~AccessibleCaret();
+
+ // This enumeration representing the visibility and visual style of an
+ // AccessibleCaret.
+ //
+ // Use SetAppearance() to change the appearance, and use GetAppearance() to
+ // get the current appearance.
+ enum class Appearance : uint8_t {
+ // Do not display the caret at all.
+ None,
+
+ // Display the caret in default style.
+ Normal,
+
+ // The caret should be displayed logically but it is kept invisible to the
+ // user. This enum is the only difference between "logically visible" and
+ // "visually visible". It can be used for reasons such as:
+ // 1. Out of scroll port.
+ // 2. For UX requirement such as hide a caret in an empty text area.
+ NormalNotShown,
+
+ // Display the caret which is tilted to the left.
+ Left,
+
+ // Display the caret which is tilted to the right.
+ Right
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const Appearance& aAppearance);
+
+ Appearance GetAppearance() const { return mAppearance; }
+
+ virtual void SetAppearance(Appearance aAppearance);
+
+ // Return true if current appearance is either Normal, NormalNotShown, Left,
+ // or Right.
+ bool IsLogicallyVisible() const { return mAppearance != Appearance::None; }
+
+ // Return true if current appearance is either Normal, Left, or Right.
+ bool IsVisuallyVisible() const {
+ return (mAppearance != Appearance::None) &&
+ (mAppearance != Appearance::NormalNotShown);
+ }
+
+ // This enum represents the result returned by SetPosition().
+ enum class PositionChangedResult : uint8_t {
+ // Both position and the zoom level are not changed.
+ NotChanged,
+
+ // The position is changed. (The zoom level may or may not be changed.)
+ Position,
+
+ // Only the zoom level is changed. The position is *not* changed.
+ Zoom,
+
+ // The position is out of scroll port.
+ Invisible
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const PositionChangedResult& aResult);
+
+ virtual PositionChangedResult SetPosition(nsIFrame* aFrame, int32_t aOffset);
+
+ // Does two AccessibleCarets overlap?
+ bool Intersects(const AccessibleCaret& aCaret) const;
+
+ // Is the point within the caret's rect? The point should be relative to root
+ // frame.
+ enum class TouchArea {
+ Full, // Contains both text overlay and caret image.
+ CaretImage
+ };
+ bool Contains(const nsPoint& aPoint, TouchArea aTouchArea) const;
+
+ // The geometry center of the imaginary caret (nsCaret) to which this
+ // AccessibleCaret is attached. It is needed when dragging the caret.
+ nsPoint LogicalPosition() const { return mImaginaryCaretRect.Center(); }
+
+ // Element for 'Intersects' test. This is the container of the caret image
+ // and text-overlay elements. See CreateCaretElement() for the content
+ // structure.
+ dom::Element& CaretElement() const { return *mCaretElementHolder->Host(); }
+
+ // Ensures that the caret element is made "APZ aware" so that the APZ code
+ // doesn't scroll the page when the user is trying to drag the caret.
+ void EnsureApzAware();
+
+ bool IsInPositionFixedSubtree() const;
+
+ protected:
+ // Argument aRect should be relative to CustomContentContainerFrame().
+ void SetCaretElementStyle(const nsRect& aRect, float aZoomLevel);
+ void SetTextOverlayElementStyle(const nsRect& aRect, float aZoomLevel);
+ void SetCaretImageElementStyle(const nsRect& aRect, float aZoomLevel);
+
+ // Get current zoom level.
+ float GetZoomLevel();
+
+ // Element which contains the text overly for the 'Contains' test.
+ dom::Element* TextOverlayElement() const;
+
+ // Element which contains the caret image for 'Contains' test.
+ dom::Element* CaretImageElement() const;
+
+ nsIFrame* RootFrame() const;
+
+ nsIFrame* CustomContentContainerFrame() const;
+
+ // Transform Appearance to CSS id used in ua.css.
+ static nsAutoString AppearanceString(Appearance aAppearance);
+
+ void CreateCaretElement() const;
+
+ // Inject caret element into custom content container.
+ void InjectCaretElement(dom::Document*);
+
+ // Remove caret element from custom content container.
+ void RemoveCaretElement(dom::Document*);
+
+ // Clear the cached rects and zoom level.
+ void ClearCachedData();
+
+ // The top-center of the imaginary caret to which this AccessibleCaret is
+ // attached.
+ static nsPoint CaretElementPosition(const nsRect& aRect) {
+ return aRect.TopLeft() + nsPoint(aRect.width / 2, 0);
+ }
+
+ class DummyTouchListener final : public nsIDOMEventListener {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD HandleEvent(mozilla::dom::Event* aEvent) override {
+ return NS_OK;
+ }
+
+ private:
+ virtual ~DummyTouchListener() = default;
+ };
+
+ // Member variables
+ Appearance mAppearance = Appearance::None;
+
+ // AccessibleCaretManager owns us by a UniquePtr. When it's terminated by
+ // AccessibleCaretEventHub::Terminate() which is called in
+ // PresShell::Destroy(), it frees us automatically. No need to worry if we
+ // outlive mPresShell.
+ PresShell* const MOZ_NON_OWNING_REF mPresShell = nullptr;
+
+ RefPtr<dom::AnonymousContent> mCaretElementHolder;
+
+ // This cached rect is relative to the root frame, and is used in
+ // LogicalPosition() when dragging a caret.
+ nsRect mImaginaryCaretRect;
+
+ // This cached rect is relative to the custom content container, and is used
+ // in SetPosition() to check whether the caret position has changed.
+ nsRect mImaginaryCaretRectInContainerFrame;
+
+ // The reference frame we used to calculate mImaginaryCaretRect and
+ // mImaginaryCaretRectInContainerFrame.
+ WeakFrame mImaginaryCaretReferenceFrame;
+
+ // Cache current zoom level to determine whether position is changed.
+ float mZoomLevel = 0.0f;
+
+ // A no-op touch-start listener which prevents APZ from panning when dragging
+ // the caret.
+ RefPtr<DummyTouchListener> mDummyTouchListener{new DummyTouchListener()};
+}; // class AccessibleCaret
+
+std::ostream& operator<<(std::ostream& aStream,
+ const AccessibleCaret::Appearance& aAppearance);
+
+std::ostream& operator<<(std::ostream& aStream,
+ const AccessibleCaret::PositionChangedResult& aResult);
+
+} // namespace mozilla
+
+#endif // AccessibleCaret_h__
diff --git a/layout/base/AccessibleCaretEventHub.cpp b/layout/base/AccessibleCaretEventHub.cpp
new file mode 100644
index 0000000000..d9cd91d866
--- /dev/null
+++ b/layout/base/AccessibleCaretEventHub.cpp
@@ -0,0 +1,702 @@
+/* -*- 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 "AccessibleCaretEventHub.h"
+
+#include "AccessibleCaretLogger.h"
+#include "AccessibleCaretManager.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "nsCanvasFrame.h"
+#include "nsDocShell.h"
+#include "nsFocusManager.h"
+#include "nsFrameSelection.h"
+#include "nsITimer.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+#undef AC_LOG
+#define AC_LOG(message, ...) \
+ AC_LOG_BASE("AccessibleCaretEventHub (%p): " message, this, ##__VA_ARGS__);
+
+#undef AC_LOGV
+#define AC_LOGV(message, ...) \
+ AC_LOGV_BASE("AccessibleCaretEventHub (%p): " message, this, ##__VA_ARGS__);
+
+NS_IMPL_ISUPPORTS(AccessibleCaretEventHub, nsIReflowObserver, nsIScrollObserver,
+ nsISupportsWeakReference);
+
+// -----------------------------------------------------------------------------
+// NoActionState
+//
+class AccessibleCaretEventHub::NoActionState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "NoActionState"; }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint, int32_t aTouchId,
+ EventClassID aEventClass) override {
+ nsEventStatus rv = nsEventStatus_eIgnore;
+
+ if (NS_SUCCEEDED(aContext->mManager->PressCaret(aPoint, aEventClass))) {
+ aContext->SetState(AccessibleCaretEventHub::PressCaretState());
+ rv = nsEventStatus_eConsumeNoDefault;
+ } else {
+ aContext->SetState(AccessibleCaretEventHub::PressNoCaretState());
+ }
+
+ aContext->mPressPoint = aPoint;
+ aContext->mActiveTouchId = aTouchId;
+
+ return rv;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollStart(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(AccessibleCaretEventHub::ScrollState());
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollPositionChanged(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollPositionChanged();
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnSelectionChanged(AccessibleCaretEventHub* aContext, Document* aDoc,
+ dom::Selection* aSel, int16_t aReason) override {
+ aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override {
+ aContext->mManager->OnBlur();
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnReflow(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnReflow();
+ }
+
+ void Enter(AccessibleCaretEventHub* aContext) override {
+ aContext->mPressPoint = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ aContext->mActiveTouchId = kInvalidTouchId;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// PressCaretState: Because we've pressed on the caret, always consume the
+// event, both real and synthesized, so that other event handling code won't
+// have a chance to do something else to interrupt caret dragging.
+//
+class AccessibleCaretEventHub::PressCaretState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "PressCaretState"; }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnMove(AccessibleCaretEventHub* aContext, const nsPoint& aPoint,
+ WidgetMouseEvent::Reason aReason) override {
+ if (aReason == WidgetMouseEvent::eReal &&
+ aContext->MoveDistanceIsLarge(aPoint)) {
+ if (NS_SUCCEEDED(aContext->mManager->DragCaret(aPoint))) {
+ aContext->SetState(AccessibleCaretEventHub::DragCaretState());
+ }
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->ReleaseCaret();
+ aContext->mManager->TapCaret(aContext->mPressPoint);
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override {
+ return nsEventStatus_eConsumeNoDefault;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// DragCaretState: Because we've pressed on the caret, always consume the event,
+// both real and synthesized, so that other event handling code won't have a
+// chance to do something else to interrupt caret dragging.
+//
+class AccessibleCaretEventHub::DragCaretState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "DragCaretState"; }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnMove(AccessibleCaretEventHub* aContext, const nsPoint& aPoint,
+ WidgetMouseEvent::Reason aReason) override {
+ if (aReason == WidgetMouseEvent::eReal) {
+ aContext->mManager->DragCaret(aPoint);
+ }
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->ReleaseCaret();
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+
+ return nsEventStatus_eConsumeNoDefault;
+ }
+};
+
+// -----------------------------------------------------------------------------
+// PressNoCaretState
+//
+class AccessibleCaretEventHub::PressNoCaretState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "PressNoCaretState"; }
+
+ nsEventStatus OnMove(AccessibleCaretEventHub* aContext, const nsPoint& aPoint,
+ WidgetMouseEvent::Reason aReason) override {
+ if (aContext->MoveDistanceIsLarge(aPoint)) {
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+ }
+
+ return nsEventStatus_eIgnore;
+ }
+
+ nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override {
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override {
+ aContext->SetState(AccessibleCaretEventHub::LongTapState());
+
+ return aContext->GetState()->OnLongTap(aContext, aPoint);
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollStart(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(AccessibleCaretEventHub::ScrollState());
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override {
+ aContext->mManager->OnBlur();
+ if (aIsLeavingDocument) {
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+ }
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnSelectionChanged(AccessibleCaretEventHub* aContext, Document* aDoc,
+ dom::Selection* aSel, int16_t aReason) override {
+ aContext->mManager->OnSelectionChanged(aDoc, aSel, aReason);
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnReflow(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnReflow();
+ }
+
+ void Enter(AccessibleCaretEventHub* aContext) override {
+ aContext->LaunchLongTapInjector();
+ }
+
+ void Leave(AccessibleCaretEventHub* aContext) override {
+ aContext->CancelLongTapInjector();
+ }
+};
+
+// -----------------------------------------------------------------------------
+// ScrollState
+//
+class AccessibleCaretEventHub::ScrollState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "ScrollState"; }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollEnd(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollEnd();
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollPositionChanged(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollPositionChanged();
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) override {
+ aContext->mManager->OnBlur();
+ if (aIsLeavingDocument) {
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+ }
+ }
+};
+
+// -----------------------------------------------------------------------------
+// LongTapState
+//
+class AccessibleCaretEventHub::LongTapState
+ : public AccessibleCaretEventHub::State {
+ public:
+ const char* Name() const override { return "LongTapState"; }
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) override {
+ // In general text selection is lower-priority than the context menu. If
+ // we consume this long-press event, then it prevents the context menu from
+ // showing up on desktop Firefox (because that happens on long-tap-up, if
+ // the long-tap was not cancelled). So we return eIgnore instead.
+ aContext->mManager->SelectWordOrShortcut(aPoint);
+ return nsEventStatus_eIgnore;
+ }
+
+ nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) override {
+ aContext->SetState(AccessibleCaretEventHub::NoActionState());
+
+ // Do not consume the release since the press is not consumed in
+ // PressNoCaretState either.
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnScrollStart(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnScrollStart();
+ aContext->SetState(AccessibleCaretEventHub::ScrollState());
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnReflow(AccessibleCaretEventHub* aContext) override {
+ aContext->mManager->OnReflow();
+ }
+};
+
+// -----------------------------------------------------------------------------
+// Implementation of AccessibleCaretEventHub methods
+//
+AccessibleCaretEventHub::State* AccessibleCaretEventHub::GetState() const {
+ return mState;
+}
+
+void AccessibleCaretEventHub::SetState(State* aState) {
+ MOZ_ASSERT(aState);
+
+ AC_LOG("%s -> %s", mState->Name(), aState->Name());
+
+ mState->Leave(this);
+ mState = aState;
+ mState->Enter(this);
+}
+
+MOZ_IMPL_STATE_CLASS_GETTER(NoActionState)
+MOZ_IMPL_STATE_CLASS_GETTER(PressCaretState)
+MOZ_IMPL_STATE_CLASS_GETTER(DragCaretState)
+MOZ_IMPL_STATE_CLASS_GETTER(PressNoCaretState)
+MOZ_IMPL_STATE_CLASS_GETTER(ScrollState)
+MOZ_IMPL_STATE_CLASS_GETTER(LongTapState)
+
+AccessibleCaretEventHub::AccessibleCaretEventHub(PresShell* aPresShell)
+ : mPresShell(aPresShell) {}
+
+void AccessibleCaretEventHub::Init() {
+ if (mInitialized || !mPresShell) {
+ return;
+ }
+
+ // Without nsAutoScriptBlocker, the script might be run after constructing
+ // mFirstCaret in AccessibleCaretManager's constructor, which might destructs
+ // the whole frame tree. Therefore we'll fail to construct mSecondCaret
+ // because we cannot get root frame or canvas frame from mPresShell to inject
+ // anonymous content. To avoid that, we protect Init() by nsAutoScriptBlocker.
+ // To reproduce, run "./mach crashtest layout/base/crashtests/897852.html"
+ // without the following scriptBlocker.
+ nsAutoScriptBlocker scriptBlocker;
+
+ nsPresContext* presContext = mPresShell->GetPresContext();
+ MOZ_ASSERT(presContext, "PresContext should be given in PresShell::Init()");
+
+ nsDocShell* docShell = presContext->GetDocShell();
+ if (!docShell) {
+ return;
+ }
+
+ docShell->AddWeakReflowObserver(this);
+ docShell->AddWeakScrollObserver(this);
+
+ mDocShell = static_cast<nsDocShell*>(docShell);
+
+ if (StaticPrefs::layout_accessiblecaret_use_long_tap_injector()) {
+ mLongTapInjectorTimer = NS_NewTimer();
+ }
+
+ mManager = MakeUnique<AccessibleCaretManager>(mPresShell);
+
+ mInitialized = true;
+}
+
+void AccessibleCaretEventHub::Terminate() {
+ if (!mInitialized) {
+ return;
+ }
+
+ if (mDocShell) {
+ mDocShell->RemoveWeakReflowObserver(this);
+ mDocShell->RemoveWeakScrollObserver(this);
+ }
+
+ if (mLongTapInjectorTimer) {
+ mLongTapInjectorTimer->Cancel();
+ }
+
+ mManager->Terminate();
+ mPresShell = nullptr;
+ mInitialized = false;
+}
+
+nsEventStatus AccessibleCaretEventHub::HandleEvent(WidgetEvent* aEvent) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (!mInitialized) {
+ return status;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ switch (aEvent->mClass) {
+ case eMouseEventClass:
+ status = HandleMouseEvent(aEvent->AsMouseEvent());
+ break;
+
+ case eTouchEventClass:
+ status = HandleTouchEvent(aEvent->AsTouchEvent());
+ break;
+
+ case eKeyboardEventClass:
+ status = HandleKeyboardEvent(aEvent->AsKeyboardEvent());
+ break;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "PresShell should've filtered unwanted event classes!");
+ break;
+ }
+
+ return status;
+}
+
+nsEventStatus AccessibleCaretEventHub::HandleMouseEvent(
+ WidgetMouseEvent* aEvent) {
+ nsEventStatus rv = nsEventStatus_eIgnore;
+
+ if (aEvent->mButton != MouseButton::ePrimary) {
+ return rv;
+ }
+
+ int32_t id =
+ (mActiveTouchId == kInvalidTouchId ? kDefaultTouchId : mActiveTouchId);
+ nsPoint point = GetMouseEventPosition(aEvent);
+
+ if (aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp ||
+ aEvent->mMessage == eMouseClick ||
+ aEvent->mMessage == eMouseDoubleClick ||
+ aEvent->mMessage == eMouseLongTap) {
+ // Don't reset the source on mouse movement since that can
+ // happen anytime, even randomly during a touch sequence.
+ mManager->SetLastInputSource(aEvent->mInputSource);
+ }
+
+ switch (aEvent->mMessage) {
+ case eMouseDown:
+ AC_LOGV("Before eMouseDown, state: %s", mState->Name());
+ rv = mState->OnPress(this, point, id, eMouseEventClass);
+ AC_LOGV("After eMouseDown, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eMouseMove:
+ AC_LOGV("Before eMouseMove, state: %s", mState->Name());
+ // The mouse move events synthesized from the touch move events can have
+ // wrong point (bug 1549355). Workaround it by ignoring the events when
+ // dragging the caret because the caret doesn't really need them.
+ rv = mState->OnMove(this, point, aEvent->mReason);
+ AC_LOGV("After eMouseMove, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eMouseUp:
+ AC_LOGV("Before eMouseUp, state: %s", mState->Name());
+ rv = mState->OnRelease(this);
+ AC_LOGV("After eMouseUp, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eMouseLongTap:
+ AC_LOGV("Before eMouseLongTap, state: %s", mState->Name());
+ rv = mState->OnLongTap(this, point);
+ AC_LOGV("After eMouseLongTap, state: %s, consume: %d", mState->Name(),
+ rv);
+ break;
+
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+nsEventStatus AccessibleCaretEventHub::HandleTouchEvent(
+ WidgetTouchEvent* aEvent) {
+ if (aEvent->mTouches.IsEmpty()) {
+ AC_LOG("%s: Receive a touch event without any touch data!", __FUNCTION__);
+ return nsEventStatus_eIgnore;
+ }
+
+ nsEventStatus rv = nsEventStatus_eIgnore;
+
+ int32_t id =
+ (mActiveTouchId == kInvalidTouchId ? aEvent->mTouches[0]->Identifier()
+ : mActiveTouchId);
+ nsPoint point = GetTouchEventPosition(aEvent, id);
+
+ mManager->SetLastInputSource(MouseEvent_Binding::MOZ_SOURCE_TOUCH);
+
+ switch (aEvent->mMessage) {
+ case eTouchStart:
+ AC_LOGV("Before eTouchStart, state: %s", mState->Name());
+ rv = mState->OnPress(this, point, id, eTouchEventClass);
+ AC_LOGV("After eTouchStart, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eTouchMove:
+ AC_LOGV("Before eTouchMove, state: %s", mState->Name());
+ // There is no synthesized touch move event.
+ rv = mState->OnMove(this, point, WidgetMouseEvent::eReal);
+ AC_LOGV("After eTouchMove, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eTouchEnd:
+ AC_LOGV("Before eTouchEnd, state: %s", mState->Name());
+ rv = mState->OnRelease(this);
+ AC_LOGV("After eTouchEnd, state: %s, consume: %d", mState->Name(), rv);
+ break;
+
+ case eTouchCancel:
+ AC_LOGV("Got eTouchCancel, state: %s", mState->Name());
+ // Do nothing since we don't really care eTouchCancel anyway.
+ break;
+
+ default:
+ break;
+ }
+
+ return rv;
+}
+
+nsEventStatus AccessibleCaretEventHub::HandleKeyboardEvent(
+ WidgetKeyboardEvent* aEvent) {
+ mManager->SetLastInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD);
+
+ switch (aEvent->mMessage) {
+ case eKeyUp:
+ AC_LOGV("eKeyUp, state: %s", mState->Name());
+ mManager->OnKeyboardEvent();
+ break;
+
+ case eKeyDown:
+ AC_LOGV("eKeyDown, state: %s", mState->Name());
+ mManager->OnKeyboardEvent();
+ break;
+
+ case eKeyPress:
+ AC_LOGV("eKeyPress, state: %s", mState->Name());
+ mManager->OnKeyboardEvent();
+ break;
+
+ default:
+ break;
+ }
+
+ return nsEventStatus_eIgnore;
+}
+
+bool AccessibleCaretEventHub::MoveDistanceIsLarge(const nsPoint& aPoint) const {
+ nsPoint delta = aPoint - mPressPoint;
+ return NS_hypot(delta.x, delta.y) >
+ AppUnitsPerCSSPixel() * kMoveStartToleranceInPixel;
+}
+
+void AccessibleCaretEventHub::LaunchLongTapInjector() {
+ if (!mLongTapInjectorTimer) {
+ return;
+ }
+
+ int32_t longTapDelay = StaticPrefs::ui_click_hold_context_menus_delay();
+ mLongTapInjectorTimer->InitWithNamedFuncCallback(
+ FireLongTap, this, longTapDelay, nsITimer::TYPE_ONE_SHOT,
+ "AccessibleCaretEventHub::LaunchLongTapInjector");
+}
+
+void AccessibleCaretEventHub::CancelLongTapInjector() {
+ if (!mLongTapInjectorTimer) {
+ return;
+ }
+
+ mLongTapInjectorTimer->Cancel();
+}
+
+/* static */
+void AccessibleCaretEventHub::FireLongTap(nsITimer* aTimer,
+ void* aAccessibleCaretEventHub) {
+ RefPtr<AccessibleCaretEventHub> self =
+ static_cast<AccessibleCaretEventHub*>(aAccessibleCaretEventHub);
+ self->mState->OnLongTap(self, self->mPressPoint);
+}
+
+NS_IMETHODIMP
+AccessibleCaretEventHub::Reflow(DOMHighResTimeStamp aStart,
+ DOMHighResTimeStamp aEnd) {
+ if (!mInitialized) {
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ if (mIsInReflowCallback) {
+ return NS_OK;
+ }
+
+ AutoRestore<bool> autoRestoreIsInReflowCallback(mIsInReflowCallback);
+ mIsInReflowCallback = true;
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnReflow(this);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+AccessibleCaretEventHub::ReflowInterruptible(DOMHighResTimeStamp aStart,
+ DOMHighResTimeStamp aEnd) {
+ // Defer the error checking to Reflow().
+ return Reflow(aStart, aEnd);
+}
+
+void AccessibleCaretEventHub::AsyncPanZoomStarted() {
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollStart(this);
+}
+
+void AccessibleCaretEventHub::AsyncPanZoomStopped() {
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollEnd(this);
+}
+
+void AccessibleCaretEventHub::ScrollPositionChanged() {
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnScrollPositionChanged(this);
+}
+
+void AccessibleCaretEventHub::OnSelectionChange(Document* aDoc,
+ dom::Selection* aSel,
+ int16_t aReason) {
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s, reason: %d", __FUNCTION__, mState->Name(), aReason);
+
+ // XXX Here we may be in a hot path. So, if we could avoid this virtual call,
+ // we should do so.
+ mState->OnSelectionChanged(this, aDoc, aSel, aReason);
+}
+
+bool AccessibleCaretEventHub::ShouldDisableApz() const {
+ return mManager && mManager->ShouldDisableApz();
+}
+
+void AccessibleCaretEventHub::NotifyBlur(bool aIsLeavingDocument) {
+ if (!mInitialized) {
+ return;
+ }
+
+ MOZ_ASSERT(mRefCnt.get() > 1, "Expect caller holds us as well!");
+
+ AC_LOG("%s, state: %s", __FUNCTION__, mState->Name());
+ mState->OnBlur(this, aIsLeavingDocument);
+}
+
+nsPoint AccessibleCaretEventHub::GetTouchEventPosition(
+ WidgetTouchEvent* aEvent, int32_t aIdentifier) const {
+ for (dom::Touch* touch : aEvent->mTouches) {
+ if (touch->Identifier() == aIdentifier) {
+ LayoutDeviceIntPoint touchIntPoint = touch->mRefPoint;
+
+ // Get event coordinate relative to root frame.
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ return nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, touchIntPoint, RelativeTo{rootFrame});
+ }
+ }
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+}
+
+nsPoint AccessibleCaretEventHub::GetMouseEventPosition(
+ WidgetMouseEvent* aEvent) const {
+ LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->mRefPoint;
+
+ // Get event coordinate relative to root frame.
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ return nsLayoutUtils::GetEventCoordinatesRelativeTo(aEvent, mouseIntPoint,
+ RelativeTo{rootFrame});
+}
+
+} // namespace mozilla
diff --git a/layout/base/AccessibleCaretEventHub.h b/layout/base/AccessibleCaretEventHub.h
new file mode 100644
index 0000000000..5b1ed581c7
--- /dev/null
+++ b/layout/base/AccessibleCaretEventHub.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 mozilla_AccessibleCaretEventHub_h
+#define mozilla_AccessibleCaretEventHub_h
+
+#include "LayoutConstants.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDocShell.h"
+#include "nsIReflowObserver.h"
+#include "nsIScrollObserver.h"
+#include "nsPoint.h"
+#include "mozilla/RefPtr.h"
+#include "nsWeakReference.h"
+
+class nsITimer;
+
+namespace mozilla {
+class AccessibleCaretManager;
+class PresShell;
+class WidgetKeyboardEvent;
+class WidgetMouseEvent;
+class WidgetTouchEvent;
+
+// -----------------------------------------------------------------------------
+// Each PresShell holds a shared pointer to an AccessibleCaretEventHub; each
+// AccessibleCaretEventHub holds a unique pointer to an AccessibleCaretManager.
+// Thus, there's one AccessibleCaretManager per PresShell.
+//
+// AccessibleCaretEventHub implements a state pattern. It receives events from
+// PresShell and callbacks by observers and listeners, and then relays them to
+// the current concrete state which calls necessary event-handling methods in
+// AccessibleCaretManager.
+//
+// We separate AccessibleCaretEventHub from AccessibleCaretManager to make the
+// state transitions in AccessibleCaretEventHub testable. We put (nearly) all
+// the operations involving PresShell, Selection, and AccessibleCaret
+// manipulation in AccessibleCaretManager so that we can mock methods in
+// AccessibleCaretManager in gtest. We test the correctness of the state
+// transitions by giving events, callbacks, and the return values by mocked
+// methods of AccessibleCaretEventHub. See TestAccessibleCaretEventHub.cpp.
+//
+// Besides dealing with real events, AccessibleCaretEventHub could also
+// synthesize fake long-tap events and inject those events to itself on the
+// platform lacks eMouseLongTap. Turn on this preference
+// "layout.accessiblecaret.use_long_tap_injector" for the fake long-tap events.
+//
+// Please see the in-tree document for state transition diagram and more
+// information.
+// HTML: https://firefox-source-docs.mozilla.org/layout/AccessibleCaret.html
+// Source rst: layout/docs/AccessibleCaret.rst
+//
+class AccessibleCaretEventHub : public nsIReflowObserver,
+ public nsIScrollObserver,
+ public nsSupportsWeakReference {
+ public:
+ explicit AccessibleCaretEventHub(PresShell* aPresShell);
+ void Init();
+ void Terminate();
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus HandleEvent(WidgetEvent* aEvent);
+
+ // Call this function to notify the blur event happened.
+ MOZ_CAN_RUN_SCRIPT
+ void NotifyBlur(bool aIsLeavingDocument);
+
+ NS_DECL_ISUPPORTS
+
+ // nsIReflowObserver
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD Reflow(DOMHighResTimeStamp start, DOMHighResTimeStamp end) final;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ NS_IMETHOD ReflowInterruptible(DOMHighResTimeStamp start,
+ DOMHighResTimeStamp end) final;
+
+ // Override nsIScrollObserver methods.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ virtual void ScrollPositionChanged() override;
+ MOZ_CAN_RUN_SCRIPT
+ virtual void AsyncPanZoomStarted() override;
+ MOZ_CAN_RUN_SCRIPT
+ virtual void AsyncPanZoomStopped() override;
+
+ // Base state
+ class State;
+ State* GetState() const;
+
+ MOZ_CAN_RUN_SCRIPT
+ void OnSelectionChange(dom::Document* aDocument, dom::Selection* aSelection,
+ int16_t aReason);
+
+ bool ShouldDisableApz() const;
+
+ protected:
+ virtual ~AccessibleCaretEventHub() = default;
+
+#define MOZ_DECL_STATE_CLASS_GETTER(aClassName) \
+ class aClassName; \
+ static State* aClassName();
+
+#define MOZ_IMPL_STATE_CLASS_GETTER(aClassName) \
+ AccessibleCaretEventHub::State* AccessibleCaretEventHub::aClassName() { \
+ static class aClassName singleton; \
+ return &singleton; \
+ }
+
+ // Concrete state getters
+ MOZ_DECL_STATE_CLASS_GETTER(NoActionState)
+ MOZ_DECL_STATE_CLASS_GETTER(PressCaretState)
+ MOZ_DECL_STATE_CLASS_GETTER(DragCaretState)
+ MOZ_DECL_STATE_CLASS_GETTER(PressNoCaretState)
+ MOZ_DECL_STATE_CLASS_GETTER(ScrollState)
+ MOZ_DECL_STATE_CLASS_GETTER(LongTapState)
+
+ void SetState(State* aState);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus HandleMouseEvent(WidgetMouseEvent* aEvent);
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus HandleTouchEvent(WidgetTouchEvent* aEvent);
+ MOZ_CAN_RUN_SCRIPT
+ nsEventStatus HandleKeyboardEvent(WidgetKeyboardEvent* aEvent);
+
+ virtual nsPoint GetTouchEventPosition(WidgetTouchEvent* aEvent,
+ int32_t aIdentifier) const;
+ virtual nsPoint GetMouseEventPosition(WidgetMouseEvent* aEvent) const;
+
+ bool MoveDistanceIsLarge(const nsPoint& aPoint) const;
+
+ void LaunchLongTapInjector();
+ void CancelLongTapInjector();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static void FireLongTap(nsITimer* aTimer, void* aAccessibleCaretEventHub);
+
+ void LaunchScrollEndInjector();
+ void CancelScrollEndInjector();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static void FireScrollEnd(nsITimer* aTimer, void* aAccessibleCaretEventHub);
+
+ // Member variables
+ State* mState = NoActionState();
+
+ // Will be set to nullptr in Terminate().
+ PresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
+
+ UniquePtr<AccessibleCaretManager> mManager;
+
+ WeakPtr<nsDocShell> mDocShell;
+
+ // Use this timer for injecting a long tap event when APZ is disabled. If APZ
+ // is enabled, it will send long tap event to us.
+ nsCOMPtr<nsITimer> mLongTapInjectorTimer;
+
+ // Last mouse button down event or touch start event point.
+ nsPoint mPressPoint{NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE};
+
+ // For filter multitouch event
+ int32_t mActiveTouchId = kInvalidTouchId;
+
+ // Flag to indicate the class has been initialized.
+ bool mInitialized = false;
+
+ // Flag to avoid calling Reflow() callback recursively.
+ bool mIsInReflowCallback = false;
+
+ static const int32_t kMoveStartToleranceInPixel = 5;
+ static const int32_t kInvalidTouchId = -1;
+ static const int32_t kDefaultTouchId = 0; // For mouse event
+};
+
+// -----------------------------------------------------------------------------
+// The base class for concrete states. A concrete state should inherit from this
+// class, and override the methods to handle the events or callbacks. A concrete
+// state is also responsible for transforming itself to the next concrete state.
+//
+class AccessibleCaretEventHub::State {
+ public:
+ virtual const char* Name() const { return ""; }
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsEventStatus OnPress(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint, int32_t aTouchId,
+ EventClassID aEventClass) {
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsEventStatus OnMove(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint,
+ WidgetMouseEvent::Reason aReason) {
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsEventStatus OnRelease(AccessibleCaretEventHub* aContext) {
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsEventStatus OnLongTap(AccessibleCaretEventHub* aContext,
+ const nsPoint& aPoint) {
+ return nsEventStatus_eIgnore;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollStart(AccessibleCaretEventHub* aContext) {}
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollEnd(AccessibleCaretEventHub* aContext) {}
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollPositionChanged(AccessibleCaretEventHub* aContext) {}
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnBlur(AccessibleCaretEventHub* aContext,
+ bool aIsLeavingDocument) {}
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnSelectionChanged(AccessibleCaretEventHub* aContext,
+ dom::Document* aDoc, dom::Selection* aSel,
+ int16_t aReason) {}
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnReflow(AccessibleCaretEventHub* aContext) {}
+ virtual void Enter(AccessibleCaretEventHub* aContext) {}
+ virtual void Leave(AccessibleCaretEventHub* aContext) {}
+
+ explicit State() = default;
+ virtual ~State() = default;
+ State(const State&) = delete;
+ State& operator=(const State&) = delete;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AccessibleCaretEventHub_h
diff --git a/layout/base/AccessibleCaretLogger.h b/layout/base/AccessibleCaretLogger.h
new file mode 100644
index 0000000000..40d0075de4
--- /dev/null
+++ b/layout/base/AccessibleCaretLogger.h
@@ -0,0 +1,28 @@
+/* -*- 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 AccessibleCaretLog_h
+#define AccessibleCaretLog_h
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+static LazyLogModule sAccessibleCaretLog("AccessibleCaret");
+
+#ifndef AC_LOG_BASE
+# define AC_LOG_BASE(...) \
+ MOZ_LOG(sAccessibleCaretLog, mozilla::LogLevel::Debug, (__VA_ARGS__));
+#endif
+
+#ifndef AC_LOGV_BASE
+# define AC_LOGV_BASE(...) \
+ MOZ_LOG(sAccessibleCaretLog, LogLevel::Verbose, (__VA_ARGS__));
+#endif
+
+} // namespace mozilla
+
+#endif // AccessibleCaretLog_h
diff --git a/layout/base/AccessibleCaretManager.cpp b/layout/base/AccessibleCaretManager.cpp
new file mode 100644
index 0000000000..17597d287d
--- /dev/null
+++ b/layout/base/AccessibleCaretManager.cpp
@@ -0,0 +1,1506 @@
+/* -*- 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 "AccessibleCaretManager.h"
+
+#include <utility>
+
+#include "AccessibleCaret.h"
+#include "AccessibleCaretEventHub.h"
+#include "AccessibleCaretLogger.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/dom/NodeFilterBinding.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/TreeWalker.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/IntegerPrintfMacros.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/SelectionMovementUtils.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsCaret.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsDebug.h"
+#include "nsFocusManager.h"
+#include "nsIFrame.h"
+#include "nsFrameSelection.h"
+#include "nsGenericHTMLElement.h"
+#include "nsIHapticFeedback.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsServiceManagerUtils.h"
+
+namespace mozilla {
+
+#undef AC_LOG
+#define AC_LOG(message, ...) \
+ AC_LOG_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
+
+#undef AC_LOGV
+#define AC_LOGV(message, ...) \
+ AC_LOGV_BASE("AccessibleCaretManager (%p): " message, this, ##__VA_ARGS__);
+
+using namespace dom;
+using Appearance = AccessibleCaret::Appearance;
+using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+
+#define AC_PROCESS_ENUM_TO_STREAM(e) \
+ case (e): \
+ aStream << #e; \
+ break;
+std::ostream& operator<<(std::ostream& aStream,
+ const AccessibleCaretManager::CaretMode& aCaretMode) {
+ using CaretMode = AccessibleCaretManager::CaretMode;
+ switch (aCaretMode) {
+ AC_PROCESS_ENUM_TO_STREAM(CaretMode::None);
+ AC_PROCESS_ENUM_TO_STREAM(CaretMode::Cursor);
+ AC_PROCESS_ENUM_TO_STREAM(CaretMode::Selection);
+ }
+ return aStream;
+}
+
+std::ostream& operator<<(
+ std::ostream& aStream,
+ const AccessibleCaretManager::UpdateCaretsHint& aHint) {
+ using UpdateCaretsHint = AccessibleCaretManager::UpdateCaretsHint;
+ switch (aHint) {
+ AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::Default);
+ AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::RespectOldAppearance);
+ AC_PROCESS_ENUM_TO_STREAM(UpdateCaretsHint::DispatchNoEvent);
+ }
+ return aStream;
+}
+#undef AC_PROCESS_ENUM_TO_STREAM
+
+AccessibleCaretManager::AccessibleCaretManager(PresShell* aPresShell)
+ : AccessibleCaretManager{
+ aPresShell,
+ Carets{aPresShell ? MakeUnique<AccessibleCaret>(aPresShell) : nullptr,
+ aPresShell ? MakeUnique<AccessibleCaret>(aPresShell)
+ : nullptr}} {}
+
+AccessibleCaretManager::AccessibleCaretManager(PresShell* aPresShell,
+ Carets aCarets)
+ : mPresShell{aPresShell}, mCarets{std::move(aCarets)} {}
+
+AccessibleCaretManager::LayoutFlusher::~LayoutFlusher() {
+ MOZ_RELEASE_ASSERT(!mFlushing, "Going away in MaybeFlush? Bad!");
+}
+
+void AccessibleCaretManager::Terminate() {
+ mCarets.Terminate();
+ mActiveCaret = nullptr;
+ mPresShell = nullptr;
+}
+
+nsresult AccessibleCaretManager::OnSelectionChanged(Document* aDoc,
+ Selection* aSel,
+ int16_t aReason) {
+ Selection* selection = GetSelection();
+ AC_LOG("%s: aSel: %p, GetSelection(): %p, aReason: %d", __FUNCTION__, aSel,
+ selection, aReason);
+ if (aSel != selection) {
+ return NS_OK;
+ }
+
+ // eSetSelection events from the Fennec widget IME can be generated
+ // by autoSuggest / autoCorrect composition changes, or by TYPE_REPLACE_TEXT
+ // actions, either positioning cursor for text insert, or selecting
+ // text-to-be-replaced. None should affect AccessibleCaret visibility.
+ if (aReason & nsISelectionListener::IME_REASON) {
+ return NS_OK;
+ }
+
+ // Move the cursor by JavaScript or unknown internal call.
+ if (aReason == nsISelectionListener::NO_REASON ||
+ aReason == nsISelectionListener::JS_REASON) {
+ auto mode = static_cast<ScriptUpdateMode>(
+ StaticPrefs::layout_accessiblecaret_script_change_update_mode());
+ if (mode == kScriptAlwaysShow ||
+ (mode == kScriptUpdateVisible && mCarets.HasLogicallyVisibleCaret())) {
+ UpdateCarets();
+ return NS_OK;
+ }
+ // Default for NO_REASON is to make hidden.
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ // Move cursor by keyboard.
+ if (aReason & nsISelectionListener::KEYPRESS_REASON) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ // OnBlur() might be called between mouse down and mouse up, so we hide carets
+ // upon mouse down anyway, and update carets upon mouse up.
+ if (aReason & nsISelectionListener::MOUSEDOWN_REASON) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ // Range will collapse after cutting or copying text.
+ if (aReason & (nsISelectionListener::COLLAPSETOSTART_REASON |
+ nsISelectionListener::COLLAPSETOEND_REASON)) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ // For mouse input we don't want to show the carets.
+ if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
+ mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ // When we want to hide the carets for mouse input, hide them for select
+ // all action fired by keyboard as well.
+ if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
+ mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_KEYBOARD &&
+ (aReason & nsISelectionListener::SELECTALL_REASON)) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return NS_OK;
+ }
+
+ UpdateCarets();
+ return NS_OK;
+}
+
+void AccessibleCaretManager::HideCaretsAndDispatchCaretStateChangedEvent() {
+ if (mCarets.HasLogicallyVisibleCaret()) {
+ AC_LOG("%s", __FUNCTION__);
+ mCarets.GetFirst()->SetAppearance(Appearance::None);
+ mCarets.GetSecond()->SetAppearance(Appearance::None);
+ mIsCaretPositionChanged = false;
+ DispatchCaretStateChangedEvent(CaretChangedReason::Visibilitychange);
+ }
+}
+
+auto AccessibleCaretManager::MaybeFlushLayout() -> Terminated {
+ if (mPresShell) {
+ // `MaybeFlush` doesn't access the PresShell after flushing, so it's OK to
+ // mark it as live.
+ mLayoutFlusher.MaybeFlush(MOZ_KnownLive(*mPresShell));
+ }
+
+ return IsTerminated();
+}
+
+void AccessibleCaretManager::UpdateCarets(const UpdateCaretsHintSet& aHint) {
+ if (MaybeFlushLayout() == Terminated::Yes) {
+ return;
+ }
+
+ mLastUpdateCaretMode = GetCaretMode();
+
+ switch (mLastUpdateCaretMode) {
+ case CaretMode::None:
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ break;
+ case CaretMode::Cursor:
+ UpdateCaretsForCursorMode(aHint);
+ break;
+ case CaretMode::Selection:
+ UpdateCaretsForSelectionMode(aHint);
+ break;
+ }
+
+ mDesiredAsyncPanZoomState.Update(*this);
+}
+
+bool AccessibleCaretManager::IsCaretDisplayableInCursorMode(
+ nsIFrame** aOutFrame, int32_t* aOutOffset) const {
+ RefPtr<nsCaret> caret = mPresShell->GetCaret();
+ if (!caret || !caret->IsVisible()) {
+ return false;
+ }
+
+ int32_t offset = 0;
+ nsIFrame* frame =
+ nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &offset);
+
+ if (!frame) {
+ return false;
+ }
+
+ if (!GetEditingHostForFrame(frame)) {
+ return false;
+ }
+
+ if (aOutFrame) {
+ *aOutFrame = frame;
+ }
+
+ if (aOutOffset) {
+ *aOutOffset = offset;
+ }
+
+ return true;
+}
+
+bool AccessibleCaretManager::HasNonEmptyTextContent(nsINode* aNode) const {
+ return nsContentUtils::HasNonEmptyTextContent(
+ aNode, nsContentUtils::eRecurseIntoChildren);
+}
+
+void AccessibleCaretManager::UpdateCaretsForCursorMode(
+ const UpdateCaretsHintSet& aHints) {
+ AC_LOG("%s, selection: %p", __FUNCTION__, GetSelection());
+
+ int32_t offset = 0;
+ nsIFrame* frame = nullptr;
+ if (!IsCaretDisplayableInCursorMode(&frame, &offset)) {
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return;
+ }
+
+ PositionChangedResult result = mCarets.GetFirst()->SetPosition(frame, offset);
+
+ switch (result) {
+ case PositionChangedResult::NotChanged:
+ case PositionChangedResult::Position:
+ case PositionChangedResult::Zoom:
+ if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
+ if (HasNonEmptyTextContent(GetEditingHostForFrame(frame))) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Normal);
+ } else if (
+ StaticPrefs::
+ layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
+ if (mCarets.GetFirst()->IsLogicallyVisible()) {
+ // Possible cases are: 1) SelectWordOrShortcut() sets the
+ // appearance to Normal. 2) When the caret is out of viewport and
+ // now scrolling into viewport, it has appearance NormalNotShown.
+ mCarets.GetFirst()->SetAppearance(Appearance::Normal);
+ } else {
+ // Possible cases are: a) Single tap on current empty content;
+ // OnSelectionChanged() sets the appearance to None due to
+ // MOUSEDOWN_REASON. b) Single tap on other empty content;
+ // OnBlur() sets the appearance to None.
+ //
+ // Do nothing to make the appearance remains None so that it can
+ // be distinguished from case 2). Also do not set the appearance
+ // to NormalNotShown here like the default update behavior.
+ }
+ } else {
+ mCarets.GetFirst()->SetAppearance(Appearance::NormalNotShown);
+ }
+ }
+ break;
+
+ case PositionChangedResult::Invisible:
+ mCarets.GetFirst()->SetAppearance(Appearance::NormalNotShown);
+ break;
+ }
+
+ mCarets.GetSecond()->SetAppearance(Appearance::None);
+
+ mIsCaretPositionChanged = (result == PositionChangedResult::Position);
+
+ if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) && !mActiveCaret) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
+ }
+}
+
+void AccessibleCaretManager::UpdateCaretsForSelectionMode(
+ const UpdateCaretsHintSet& aHints) {
+ AC_LOG("%s: selection: %p", __FUNCTION__, GetSelection());
+
+ int32_t startOffset = 0;
+ nsIFrame* startFrame =
+ GetFrameForFirstRangeStartOrLastRangeEnd(eDirNext, &startOffset);
+
+ int32_t endOffset = 0;
+ nsIFrame* endFrame =
+ GetFrameForFirstRangeStartOrLastRangeEnd(eDirPrevious, &endOffset);
+
+ if (!CompareTreePosition(startFrame, endFrame)) {
+ // XXX: Do we really have to hide carets if this condition isn't satisfied?
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return;
+ }
+
+ auto updateSingleCaret = [aHints](AccessibleCaret* aCaret, nsIFrame* aFrame,
+ int32_t aOffset) -> PositionChangedResult {
+ PositionChangedResult result = aCaret->SetPosition(aFrame, aOffset);
+
+ switch (result) {
+ case PositionChangedResult::NotChanged:
+ case PositionChangedResult::Position:
+ case PositionChangedResult::Zoom:
+ if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
+ aCaret->SetAppearance(Appearance::Normal);
+ }
+ break;
+
+ case PositionChangedResult::Invisible:
+ aCaret->SetAppearance(Appearance::NormalNotShown);
+ break;
+ }
+ return result;
+ };
+
+ PositionChangedResult firstCaretResult =
+ updateSingleCaret(mCarets.GetFirst(), startFrame, startOffset);
+ PositionChangedResult secondCaretResult =
+ updateSingleCaret(mCarets.GetSecond(), endFrame, endOffset);
+
+ mIsCaretPositionChanged =
+ firstCaretResult == PositionChangedResult::Position ||
+ secondCaretResult == PositionChangedResult::Position;
+
+ if (mIsCaretPositionChanged) {
+ // Flush layout to make the carets intersection correct.
+ if (MaybeFlushLayout() == Terminated::Yes) {
+ return;
+ }
+ }
+
+ if (!aHints.contains(UpdateCaretsHint::RespectOldAppearance)) {
+ // Only check for tilt carets when the caller doesn't ask us to preserve
+ // old appearance. Otherwise we might override the appearance set by the
+ // caller.
+ if (StaticPrefs::layout_accessiblecaret_always_tilt()) {
+ UpdateCaretsForAlwaysTilt(startFrame, endFrame);
+ } else {
+ UpdateCaretsForOverlappingTilt();
+ }
+ }
+
+ if (!aHints.contains(UpdateCaretsHint::DispatchNoEvent) && !mActiveCaret) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Updateposition);
+ }
+}
+
+void AccessibleCaretManager::DesiredAsyncPanZoomState::Update(
+ const AccessibleCaretManager& aAccessibleCaretManager) {
+ if (aAccessibleCaretManager.mActiveCaret) {
+ // No need to disable APZ when dragging the caret.
+ mValue = Value::Enabled;
+ return;
+ }
+
+ if (aAccessibleCaretManager.mIsScrollStarted) {
+ // During scrolling, the caret's position is changed only if it is in a
+ // position:fixed or a "stuck" position:sticky frame subtree.
+ mValue = aAccessibleCaretManager.mIsCaretPositionChanged ? Value::Disabled
+ : Value::Enabled;
+ return;
+ }
+
+ // For other cases, we can only reliably detect whether the caret is in a
+ // position:fixed frame subtree.
+ switch (aAccessibleCaretManager.mLastUpdateCaretMode) {
+ case CaretMode::None:
+ mValue = Value::Enabled;
+ break;
+ case CaretMode::Cursor:
+ mValue =
+ (aAccessibleCaretManager.mCarets.GetFirst()->IsVisuallyVisible() &&
+ aAccessibleCaretManager.mCarets.GetFirst()
+ ->IsInPositionFixedSubtree())
+ ? Value::Disabled
+ : Value::Enabled;
+ break;
+ case CaretMode::Selection:
+ mValue =
+ ((aAccessibleCaretManager.mCarets.GetFirst()->IsVisuallyVisible() &&
+ aAccessibleCaretManager.mCarets.GetFirst()
+ ->IsInPositionFixedSubtree()) ||
+ (aAccessibleCaretManager.mCarets.GetSecond()->IsVisuallyVisible() &&
+ aAccessibleCaretManager.mCarets.GetSecond()
+ ->IsInPositionFixedSubtree()))
+ ? Value::Disabled
+ : Value::Enabled;
+ break;
+ }
+}
+
+bool AccessibleCaretManager::UpdateCaretsForOverlappingTilt() {
+ if (!mCarets.GetFirst()->IsVisuallyVisible() ||
+ !mCarets.GetSecond()->IsVisuallyVisible()) {
+ return false;
+ }
+
+ if (!mCarets.GetFirst()->Intersects(*mCarets.GetSecond())) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Normal);
+ mCarets.GetSecond()->SetAppearance(Appearance::Normal);
+ return false;
+ }
+
+ if (mCarets.GetFirst()->LogicalPosition().x <=
+ mCarets.GetSecond()->LogicalPosition().x) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Left);
+ mCarets.GetSecond()->SetAppearance(Appearance::Right);
+ } else {
+ mCarets.GetFirst()->SetAppearance(Appearance::Right);
+ mCarets.GetSecond()->SetAppearance(Appearance::Left);
+ }
+
+ return true;
+}
+
+void AccessibleCaretManager::UpdateCaretsForAlwaysTilt(
+ const nsIFrame* aStartFrame, const nsIFrame* aEndFrame) {
+ // When a short LTR word in RTL environment is selected, the two carets
+ // tilted inward might be overlapped. Make them tilt outward.
+ if (UpdateCaretsForOverlappingTilt()) {
+ return;
+ }
+
+ if (mCarets.GetFirst()->IsVisuallyVisible()) {
+ auto startFrameWritingMode = aStartFrame->GetWritingMode();
+ mCarets.GetFirst()->SetAppearance(startFrameWritingMode.IsBidiLTR()
+ ? Appearance::Left
+ : Appearance::Right);
+ }
+ if (mCarets.GetSecond()->IsVisuallyVisible()) {
+ auto endFrameWritingMode = aEndFrame->GetWritingMode();
+ mCarets.GetSecond()->SetAppearance(
+ endFrameWritingMode.IsBidiLTR() ? Appearance::Right : Appearance::Left);
+ }
+}
+
+void AccessibleCaretManager::ProvideHapticFeedback() {
+ if (StaticPrefs::layout_accessiblecaret_hapticfeedback()) {
+ if (nsCOMPtr<nsIHapticFeedback> haptic =
+ do_GetService("@mozilla.org/widget/hapticfeedback;1")) {
+ haptic->PerformSimpleAction(haptic->LongPress);
+ }
+ }
+}
+
+nsresult AccessibleCaretManager::PressCaret(const nsPoint& aPoint,
+ EventClassID aEventClass) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ MOZ_ASSERT(aEventClass == eMouseEventClass || aEventClass == eTouchEventClass,
+ "Unexpected event class!");
+
+ using TouchArea = AccessibleCaret::TouchArea;
+ TouchArea touchArea =
+ aEventClass == eMouseEventClass ? TouchArea::CaretImage : TouchArea::Full;
+
+ if (mCarets.GetFirst()->Contains(aPoint, touchArea)) {
+ mActiveCaret = mCarets.GetFirst();
+ SetSelectionDirection(eDirPrevious);
+ } else if (mCarets.GetSecond()->Contains(aPoint, touchArea)) {
+ mActiveCaret = mCarets.GetSecond();
+ SetSelectionDirection(eDirNext);
+ }
+
+ if (mActiveCaret) {
+ mOffsetYToCaretLogicalPosition =
+ mActiveCaret->LogicalPosition().y - aPoint.y;
+ SetSelectionDragState(true);
+ DispatchCaretStateChangedEvent(CaretChangedReason::Presscaret, &aPoint);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+nsresult AccessibleCaretManager::DragCaret(const nsPoint& aPoint) {
+ MOZ_ASSERT(mActiveCaret);
+ MOZ_ASSERT(GetCaretMode() != CaretMode::None);
+
+ if (!mPresShell || !mPresShell->GetRootFrame() || !GetSelection()) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ StopSelectionAutoScrollTimer();
+ DragCaretInternal(aPoint);
+
+ // We want to scroll the page even if we failed to drag the caret.
+ StartSelectionAutoScrollTimer(aPoint);
+ UpdateCarets();
+
+ if (StaticPrefs::layout_accessiblecaret_magnifier_enabled()) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Dragcaret, &aPoint);
+ }
+ return NS_OK;
+}
+
+nsresult AccessibleCaretManager::ReleaseCaret() {
+ MOZ_ASSERT(mActiveCaret);
+
+ mActiveCaret = nullptr;
+ SetSelectionDragState(false);
+ mDesiredAsyncPanZoomState.Update(*this);
+ DispatchCaretStateChangedEvent(CaretChangedReason::Releasecaret);
+ return NS_OK;
+}
+
+nsresult AccessibleCaretManager::TapCaret(const nsPoint& aPoint) {
+ MOZ_ASSERT(GetCaretMode() != CaretMode::None);
+
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (GetCaretMode() == CaretMode::Cursor) {
+ DispatchCaretStateChangedEvent(CaretChangedReason::Taponcaret, &aPoint);
+ rv = NS_OK;
+ }
+
+ return rv;
+}
+
+static EnumSet<nsLayoutUtils::FrameForPointOption> GetHitTestOptions() {
+ EnumSet<nsLayoutUtils::FrameForPointOption> options = {
+ nsLayoutUtils::FrameForPointOption::IgnorePaintSuppression,
+ nsLayoutUtils::FrameForPointOption::IgnoreCrossDoc};
+ return options;
+}
+
+nsresult AccessibleCaretManager::SelectWordOrShortcut(const nsPoint& aPoint) {
+ // If the long-tap is landing on a pre-existing selection, don't replace
+ // it with a new one. Instead just return and let the context menu pop up
+ // on the pre-existing selection.
+ if (GetCaretMode() == CaretMode::Selection &&
+ GetSelection()->ContainsPoint(aPoint)) {
+ AC_LOG("%s: UpdateCarets() for current selection", __FUNCTION__);
+ UpdateCarets();
+ ProvideHapticFeedback();
+ return NS_OK;
+ }
+
+ if (!mPresShell) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ if (!rootFrame) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ // Find the frame under point.
+ AutoWeakFrame ptFrame = nsLayoutUtils::GetFrameForPoint(
+ RelativeTo{rootFrame}, aPoint, GetHitTestOptions());
+ if (!ptFrame.GetFrame()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame* focusableFrame = GetFocusableFrame(ptFrame);
+
+#ifdef DEBUG_FRAME_DUMP
+ AC_LOG("%s: Found %s under (%d, %d)", __FUNCTION__, ptFrame->ListTag().get(),
+ aPoint.x, aPoint.y);
+ AC_LOG("%s: Found %s focusable", __FUNCTION__,
+ focusableFrame ? focusableFrame->ListTag().get() : "no frame");
+#endif
+
+ // Get ptInFrame here so that we don't need to check whether rootFrame is
+ // alive later. Note that if ptFrame is being moved by
+ // IMEStateManager::NotifyIME() or ChangeFocusToOrClearOldFocus() below,
+ // something under the original point will be selected, which may not be the
+ // original text the user wants to select.
+ nsPoint ptInFrame = aPoint;
+ nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
+ ptInFrame);
+
+ // Firstly check long press on an empty editable content.
+ Element* newFocusEditingHost = GetEditingHostForFrame(ptFrame);
+ if (focusableFrame && newFocusEditingHost &&
+ !HasNonEmptyTextContent(newFocusEditingHost)) {
+ ChangeFocusToOrClearOldFocus(focusableFrame);
+
+ if (StaticPrefs::
+ layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Normal);
+ }
+ // We need to update carets to get correct information before dispatching
+ // CaretStateChangedEvent.
+ UpdateCarets();
+ ProvideHapticFeedback();
+ DispatchCaretStateChangedEvent(CaretChangedReason::Longpressonemptycontent);
+ return NS_OK;
+ }
+
+ bool selectable = ptFrame->IsSelectable(nullptr);
+
+#ifdef DEBUG_FRAME_DUMP
+ AC_LOG("%s: %s %s selectable.", __FUNCTION__, ptFrame->ListTag().get(),
+ selectable ? "is" : "is NOT");
+#endif
+
+ if (!selectable) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Commit the composition string of the old editable focus element (if there
+ // is any) before changing the focus.
+ IMEStateManager::NotifyIME(widget::REQUEST_TO_COMMIT_COMPOSITION,
+ mPresShell->GetPresContext());
+ if (!ptFrame.IsAlive()) {
+ // Cannot continue because ptFrame died.
+ return NS_ERROR_FAILURE;
+ }
+
+ // ptFrame is selectable. Now change the focus.
+ ChangeFocusToOrClearOldFocus(focusableFrame);
+ if (!ptFrame.IsAlive()) {
+ // Cannot continue because ptFrame died.
+ return NS_ERROR_FAILURE;
+ }
+
+ // If long tap point isn't selectable frame for caret and frame selection
+ // can find a better frame for caret, we don't select a word.
+ // See https://webcompat.com/issues/15953
+ nsIFrame::ContentOffsets offsets = ptFrame->GetContentOffsetsFromPoint(
+ ptInFrame,
+ nsIFrame::SKIP_HIDDEN | nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
+ if (offsets.content) {
+ RefPtr<nsFrameSelection> frameSelection = GetFrameSelection();
+ if (frameSelection) {
+ nsIFrame* theFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ offsets.content, offsets.offset, offsets.associate);
+ if (theFrame && theFrame != ptFrame) {
+ SetSelectionDragState(true);
+ frameSelection->HandleClick(
+ MOZ_KnownLive(offsets.content) /* bug 1636889 */,
+ offsets.StartOffset(), offsets.EndOffset(),
+ nsFrameSelection::FocusMode::kCollapseToNewPoint,
+ offsets.associate);
+ SetSelectionDragState(false);
+ ClearMaintainedSelection();
+
+ if (StaticPrefs::
+ layout_accessiblecaret_caret_shown_when_long_tapping_on_empty_content()) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Normal);
+ }
+
+ UpdateCarets();
+ ProvideHapticFeedback();
+ DispatchCaretStateChangedEvent(
+ CaretChangedReason::Longpressonemptycontent);
+
+ return NS_OK;
+ }
+ }
+ }
+
+ // Then try select a word under point.
+ nsresult rv = SelectWord(ptFrame, ptInFrame);
+ UpdateCarets();
+ ProvideHapticFeedback();
+
+ return rv;
+}
+
+void AccessibleCaretManager::OnScrollStart() {
+ AC_LOG("%s", __FUNCTION__);
+
+ nsAutoScriptBlocker scriptBlocker;
+ AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
+ mLayoutFlusher.mAllowFlushing = false;
+
+ Maybe<PresShell::AutoAssertNoFlush> assert;
+ if (mPresShell) {
+ assert.emplace(*mPresShell);
+ }
+
+ mIsScrollStarted = true;
+
+ if (mCarets.HasLogicallyVisibleCaret()) {
+ // Dispatch the event only if one of the carets is logically visible like in
+ // HideCaretsAndDispatchCaretStateChangedEvent().
+ DispatchCaretStateChangedEvent(CaretChangedReason::Scroll);
+ }
+}
+
+void AccessibleCaretManager::OnScrollEnd() {
+ nsAutoScriptBlocker scriptBlocker;
+ AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
+ mLayoutFlusher.mAllowFlushing = false;
+
+ Maybe<PresShell::AutoAssertNoFlush> assert;
+ if (mPresShell) {
+ assert.emplace(*mPresShell);
+ }
+
+ mIsScrollStarted = false;
+
+ if (GetCaretMode() == CaretMode::Cursor) {
+ if (!mCarets.GetFirst()->IsLogicallyVisible()) {
+ // If the caret is hidden (Appearance::None) due to blur, no
+ // need to update it.
+ return;
+ }
+ }
+
+ // For mouse and keyboard input, we don't want to show the carets.
+ if (StaticPrefs::layout_accessiblecaret_hide_carets_for_mouse_input() &&
+ (mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_MOUSE ||
+ mLastInputSource == MouseEvent_Binding::MOZ_SOURCE_KEYBOARD)) {
+ AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ return;
+ }
+
+ AC_LOG("%s: UpdateCarets()", __FUNCTION__);
+ UpdateCarets();
+}
+
+void AccessibleCaretManager::OnScrollPositionChanged() {
+ nsAutoScriptBlocker scriptBlocker;
+ AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
+ mLayoutFlusher.mAllowFlushing = false;
+
+ Maybe<PresShell::AutoAssertNoFlush> assert;
+ if (mPresShell) {
+ assert.emplace(*mPresShell);
+ }
+
+ if (mCarets.HasLogicallyVisibleCaret()) {
+ if (mIsScrollStarted) {
+ // We don't want extra CaretStateChangedEvents dispatched when user is
+ // scrolling the page.
+ AC_LOG("%s: UpdateCarets(RespectOldAppearance | DispatchNoEvent)",
+ __FUNCTION__);
+ UpdateCarets({UpdateCaretsHint::RespectOldAppearance,
+ UpdateCaretsHint::DispatchNoEvent});
+ } else {
+ AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+ UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+ }
+ }
+}
+
+void AccessibleCaretManager::OnReflow() {
+ nsAutoScriptBlocker scriptBlocker;
+ AutoRestore<bool> saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing);
+ mLayoutFlusher.mAllowFlushing = false;
+
+ Maybe<PresShell::AutoAssertNoFlush> assert;
+ if (mPresShell) {
+ assert.emplace(*mPresShell);
+ }
+
+ if (mCarets.HasLogicallyVisibleCaret()) {
+ AC_LOG("%s: UpdateCarets(RespectOldAppearance)", __FUNCTION__);
+ UpdateCarets(UpdateCaretsHint::RespectOldAppearance);
+ }
+}
+
+void AccessibleCaretManager::OnBlur() {
+ AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
+ HideCaretsAndDispatchCaretStateChangedEvent();
+}
+
+void AccessibleCaretManager::OnKeyboardEvent() {
+ if (GetCaretMode() == CaretMode::Cursor) {
+ AC_LOG("%s: HideCaretsAndDispatchCaretStateChangedEvent()", __FUNCTION__);
+ HideCaretsAndDispatchCaretStateChangedEvent();
+ }
+}
+
+void AccessibleCaretManager::SetLastInputSource(uint16_t aInputSource) {
+ mLastInputSource = aInputSource;
+}
+
+bool AccessibleCaretManager::ShouldDisableApz() const {
+ return mDesiredAsyncPanZoomState.Get() ==
+ DesiredAsyncPanZoomState::Value::Disabled;
+}
+
+Selection* AccessibleCaretManager::GetSelection() const {
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (!fs) {
+ return nullptr;
+ }
+ return fs->GetSelection(SelectionType::eNormal);
+}
+
+already_AddRefed<nsFrameSelection> AccessibleCaretManager::GetFrameSelection()
+ const {
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ // Prevent us from touching the nsFrameSelection associated with other
+ // PresShell.
+ RefPtr<nsFrameSelection> fs = mPresShell->GetLastFocusedFrameSelection();
+ if (!fs || fs->GetPresShell() != mPresShell) {
+ return nullptr;
+ }
+
+ return fs.forget();
+}
+
+nsAutoString AccessibleCaretManager::StringifiedSelection() const {
+ nsAutoString str;
+ RefPtr<Selection> selection = GetSelection();
+ if (selection) {
+ selection->Stringify(str, mLayoutFlusher.mAllowFlushing
+ ? Selection::FlushFrames::Yes
+ : Selection::FlushFrames::No);
+ }
+ return str;
+}
+
+// static
+Element* AccessibleCaretManager::GetEditingHostForFrame(
+ const nsIFrame* aFrame) {
+ if (!aFrame) {
+ return nullptr;
+ }
+
+ auto content = aFrame->GetContent();
+ if (!content) {
+ return nullptr;
+ }
+
+ return content->GetEditingHost();
+}
+
+AccessibleCaretManager::CaretMode AccessibleCaretManager::GetCaretMode() const {
+ const Selection* selection = GetSelection();
+ if (!selection) {
+ return CaretMode::None;
+ }
+
+ const uint32_t rangeCount = selection->RangeCount();
+ if (rangeCount <= 0) {
+ return CaretMode::None;
+ }
+
+ const nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ MOZ_ASSERT(fm);
+ if (fm->GetFocusedWindow() != mPresShell->GetDocument()->GetWindow()) {
+ // Hide carets if the window is not focused.
+ return CaretMode::None;
+ }
+
+ if (selection->IsCollapsed()) {
+ return CaretMode::Cursor;
+ }
+
+ return CaretMode::Selection;
+}
+
+nsIFrame* AccessibleCaretManager::GetFocusableFrame(nsIFrame* aFrame) const {
+ // This implementation is similar to EventStateManager::PostHandleEvent().
+ // Look for the nearest enclosing focusable frame.
+ nsIFrame* focusableFrame = aFrame;
+ while (focusableFrame) {
+ if (focusableFrame->IsFocusable(/* aWithMouse = */ true)) {
+ break;
+ }
+ focusableFrame = focusableFrame->GetParent();
+ }
+ return focusableFrame;
+}
+
+void AccessibleCaretManager::ChangeFocusToOrClearOldFocus(
+ nsIFrame* aFrame) const {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ MOZ_ASSERT(fm);
+
+ if (aFrame) {
+ nsIContent* focusableContent = aFrame->GetContent();
+ MOZ_ASSERT(focusableContent, "Focusable frame must have content!");
+ RefPtr<Element> focusableElement = Element::FromNode(focusableContent);
+ fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYLONGPRESS);
+ } else if (nsCOMPtr<nsPIDOMWindowOuter> win =
+ mPresShell->GetDocument()->GetWindow()) {
+ fm->ClearFocus(win);
+ fm->SetFocusedWindow(win);
+ }
+}
+
+nsresult AccessibleCaretManager::SelectWord(nsIFrame* aFrame,
+ const nsPoint& aPoint) const {
+ AC_LOGV("%s", __FUNCTION__);
+
+ SetSelectionDragState(true);
+ const RefPtr<nsPresContext> pinnedPresContext{mPresShell->GetPresContext()};
+ nsresult rs = aFrame->SelectByTypeAtPoint(pinnedPresContext, aPoint,
+ eSelectWord, eSelectWord, 0);
+
+ SetSelectionDragState(false);
+ ClearMaintainedSelection();
+
+ // Smart-select phone numbers if possible.
+ if (StaticPrefs::layout_accessiblecaret_extend_selection_for_phone_number()) {
+ SelectMoreIfPhoneNumber();
+ }
+
+ return rs;
+}
+
+void AccessibleCaretManager::SetSelectionDragState(bool aState) const {
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (fs) {
+ fs->SetDragState(aState);
+ }
+}
+
+bool AccessibleCaretManager::IsPhoneNumber(const nsAString& aCandidate) const {
+ RefPtr<Document> doc = mPresShell->GetDocument();
+ nsAutoString phoneNumberRegex(u"(^\\+)?[0-9 ,\\-.\\(\\)*#pw]{1,30}$"_ns);
+ return nsContentUtils::IsPatternMatching(aCandidate,
+ std::move(phoneNumberRegex), doc)
+ .valueOr(false);
+}
+
+void AccessibleCaretManager::SelectMoreIfPhoneNumber() const {
+ if (IsPhoneNumber(StringifiedSelection())) {
+ SetSelectionDirection(eDirNext);
+ ExtendPhoneNumberSelection(u"forward"_ns);
+
+ SetSelectionDirection(eDirPrevious);
+ ExtendPhoneNumberSelection(u"backward"_ns);
+
+ SetSelectionDirection(eDirNext);
+ }
+}
+
+void AccessibleCaretManager::ExtendPhoneNumberSelection(
+ const nsAString& aDirection) const {
+ if (!mPresShell) {
+ return;
+ }
+
+ // Extend the phone number selection until we find a boundary.
+ RefPtr<Selection> selection = GetSelection();
+
+ while (selection) {
+ const nsRange* anchorFocusRange = selection->GetAnchorFocusRange();
+ if (!anchorFocusRange) {
+ return;
+ }
+
+ // Backup the anchor focus range since both anchor node and focus node might
+ // be changed after calling Selection::Modify().
+ RefPtr<nsRange> oldAnchorFocusRange = anchorFocusRange->CloneRange();
+
+ // Save current focus node, focus offset and the selected text so that
+ // we can compare them with the modified ones later.
+ nsINode* oldFocusNode = selection->GetFocusNode();
+ uint32_t oldFocusOffset = selection->FocusOffset();
+ nsAutoString oldSelectedText = StringifiedSelection();
+
+ // Extend the selection by one char.
+ selection->Modify(u"extend"_ns, aDirection, u"character"_ns,
+ IgnoreErrors());
+ if (IsTerminated() == Terminated::Yes) {
+ return;
+ }
+
+ // If the selection didn't change, (can't extend further), we're done.
+ if (selection->GetFocusNode() == oldFocusNode &&
+ selection->FocusOffset() == oldFocusOffset) {
+ return;
+ }
+
+ // If the changed selection isn't a valid phone number, we're done.
+ // Also, if the selection was extended to a new block node, the string
+ // returned by stringify() won't have a new line at the beginning or the
+ // end of the string. Therefore, if either focus node or offset is
+ // changed, but selected text is not changed, we're done, too.
+ nsAutoString selectedText = StringifiedSelection();
+
+ if (!IsPhoneNumber(selectedText) || oldSelectedText == selectedText) {
+ // Backout the undesired selection extend, restore the old anchor focus
+ // range before exit.
+ selection->SetAnchorFocusToRange(oldAnchorFocusRange);
+ return;
+ }
+ }
+}
+
+void AccessibleCaretManager::SetSelectionDirection(nsDirection aDir) const {
+ Selection* selection = GetSelection();
+ if (selection) {
+ selection->AdjustAnchorFocusForMultiRange(aDir);
+ }
+}
+
+void AccessibleCaretManager::ClearMaintainedSelection() const {
+ // Selection made by double-clicking for example will maintain the original
+ // word selection. We should clear it so that we can drag caret freely.
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ if (fs) {
+ fs->MaintainSelection(eSelectNoAmount);
+ }
+}
+
+void AccessibleCaretManager::LayoutFlusher::MaybeFlush(
+ const PresShell& aPresShell) {
+ if (mAllowFlushing) {
+ AutoRestore<bool> flushing(mFlushing);
+ mFlushing = true;
+
+ if (Document* doc = aPresShell.GetDocument()) {
+ doc->FlushPendingNotifications(FlushType::Layout);
+ // Don't access the PresShell after flushing, it could've become invalid.
+ }
+ }
+}
+
+nsIFrame* AccessibleCaretManager::GetFrameForFirstRangeStartOrLastRangeEnd(
+ nsDirection aDirection, int32_t* aOutOffset, nsIContent** aOutContent,
+ int32_t* aOutContentOffset) const {
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
+ MOZ_ASSERT(aOutOffset, "aOutOffset shouldn't be nullptr!");
+
+ const nsRange* range = nullptr;
+ RefPtr<nsINode> startNode;
+ RefPtr<nsINode> endNode;
+ int32_t nodeOffset = 0;
+ CaretAssociationHint hint;
+
+ RefPtr<Selection> selection = GetSelection();
+ bool findInFirstRangeStart = aDirection == eDirNext;
+
+ if (findInFirstRangeStart) {
+ range = selection->GetRangeAt(0);
+ startNode = range->GetStartContainer();
+ endNode = range->GetEndContainer();
+ nodeOffset = range->StartOffset();
+ hint = CaretAssociationHint::After;
+ } else {
+ MOZ_ASSERT(selection->RangeCount() > 0);
+ range = selection->GetRangeAt(selection->RangeCount() - 1);
+ startNode = range->GetEndContainer();
+ endNode = range->GetStartContainer();
+ nodeOffset = range->EndOffset();
+ hint = CaretAssociationHint::Before;
+ }
+
+ nsCOMPtr<nsIContent> startContent = nsIContent::FromNodeOrNull(startNode);
+ uint32_t outOffset = 0;
+ nsIFrame* startFrame = SelectionMovementUtils::GetFrameForNodeOffset(
+ startContent, nodeOffset, hint, &outOffset);
+ *aOutOffset = static_cast<int32_t>(outOffset);
+
+ if (!startFrame) {
+ ErrorResult err;
+ RefPtr<TreeWalker> walker = mPresShell->GetDocument()->CreateTreeWalker(
+ *startNode, dom::NodeFilter_Binding::SHOW_ALL, nullptr, err);
+
+ if (!walker) {
+ return nullptr;
+ }
+
+ startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
+ while (!startFrame && startNode != endNode) {
+ startNode = findInFirstRangeStart ? walker->NextNode(err)
+ : walker->PreviousNode(err);
+
+ if (!startNode) {
+ break;
+ }
+
+ startContent = startNode->AsContent();
+ startFrame = startContent ? startContent->GetPrimaryFrame() : nullptr;
+ }
+
+ // We are walking among the nodes in the content tree, so the node offset
+ // relative to startNode should be set to 0.
+ nodeOffset = 0;
+ *aOutOffset = 0;
+ }
+
+ if (startFrame) {
+ if (aOutContent) {
+ startContent.forget(aOutContent);
+ }
+ if (aOutContentOffset) {
+ *aOutContentOffset = nodeOffset;
+ }
+ }
+
+ return startFrame;
+}
+
+bool AccessibleCaretManager::RestrictCaretDraggingOffsets(
+ nsIFrame::ContentOffsets& aOffsets) {
+ if (!mPresShell) {
+ return false;
+ }
+
+ MOZ_ASSERT(GetCaretMode() == CaretMode::Selection);
+
+ nsDirection dir =
+ mActiveCaret == mCarets.GetFirst() ? eDirPrevious : eDirNext;
+ int32_t offset = 0;
+ nsCOMPtr<nsIContent> content;
+ int32_t contentOffset = 0;
+ nsIFrame* frame = GetFrameForFirstRangeStartOrLastRangeEnd(
+ dir, &offset, getter_AddRefs(content), &contentOffset);
+
+ if (!frame) {
+ return false;
+ }
+
+ // Compare the active caret's new position (aOffsets) to the inactive caret's
+ // position.
+ NS_ASSERTION(contentOffset >= 0, "contentOffset should not be negative");
+ const Maybe<int32_t> cmpToInactiveCaretPos =
+ nsContentUtils::ComparePoints_AllowNegativeOffsets(
+ aOffsets.content, aOffsets.StartOffset(), content, contentOffset);
+ if (NS_WARN_IF(!cmpToInactiveCaretPos)) {
+ // Potentially handle this properly when Selection across Shadow DOM
+ // boundary is implemented
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
+ return false;
+ }
+
+ // Move one character (in the direction of dir) from the inactive caret's
+ // position. This is the limit for the active caret's new position.
+ PeekOffsetStruct limit(
+ eSelectCluster, dir, offset, nsPoint(0, 0),
+ {PeekOffsetOption::JumpLines, PeekOffsetOption::StopAtScroller});
+ nsresult rv = frame->PeekOffset(&limit);
+ if (NS_FAILED(rv)) {
+ limit.mResultContent = content;
+ limit.mContentOffset = contentOffset;
+ }
+
+ // Compare the active caret's new position (aOffsets) to the limit.
+ NS_ASSERTION(limit.mContentOffset >= 0,
+ "limit.mContentOffset should not be negative");
+ const Maybe<int32_t> cmpToLimit =
+ nsContentUtils::ComparePoints_AllowNegativeOffsets(
+ aOffsets.content, aOffsets.StartOffset(), limit.mResultContent,
+ limit.mContentOffset);
+ if (NS_WARN_IF(!cmpToLimit)) {
+ // Potentially handle this properly when Selection across Shadow DOM
+ // boundary is implemented
+ // (https://bugzilla.mozilla.org/show_bug.cgi?id=1607497).
+ return false;
+ }
+
+ auto SetOffsetsToLimit = [&aOffsets, &limit]() {
+ aOffsets.content = limit.mResultContent;
+ aOffsets.offset = limit.mContentOffset;
+ aOffsets.secondaryOffset = limit.mContentOffset;
+ };
+
+ if (!StaticPrefs::
+ layout_accessiblecaret_allow_dragging_across_other_caret()) {
+ if ((mActiveCaret == mCarets.GetFirst() && *cmpToLimit == 1) ||
+ (mActiveCaret == mCarets.GetSecond() && *cmpToLimit == -1)) {
+ // The active caret's position is past the limit, which we don't allow
+ // here. So set it to the limit, resulting in one character being
+ // selected.
+ SetOffsetsToLimit();
+ }
+ } else {
+ switch (*cmpToInactiveCaretPos) {
+ case 0:
+ // The active caret's position is the same as the position of the
+ // inactive caret. So set it to the limit to prevent the selection from
+ // being collapsed, resulting in one character being selected.
+ SetOffsetsToLimit();
+ break;
+ case 1:
+ if (mActiveCaret == mCarets.GetFirst()) {
+ // First caret was moved across the second caret. After making change
+ // to the selection, the user will drag the second caret.
+ mActiveCaret = mCarets.GetSecond();
+ }
+ break;
+ case -1:
+ if (mActiveCaret == mCarets.GetSecond()) {
+ // Second caret was moved across the first caret. After making change
+ // to the selection, the user will drag the first caret.
+ mActiveCaret = mCarets.GetFirst();
+ }
+ break;
+ }
+ }
+
+ return true;
+}
+
+bool AccessibleCaretManager::CompareTreePosition(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame) const {
+ return (aStartFrame && aEndFrame &&
+ nsLayoutUtils::CompareTreePosition(aStartFrame, aEndFrame) <= 0);
+}
+
+nsresult AccessibleCaretManager::DragCaretInternal(const nsPoint& aPoint) {
+ MOZ_ASSERT(mPresShell);
+
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ MOZ_ASSERT(rootFrame, "We need root frame to compute caret dragging!");
+
+ nsPoint point = AdjustDragBoundary(
+ nsPoint(aPoint.x, aPoint.y + mOffsetYToCaretLogicalPosition));
+
+ // Find out which content we point to
+
+ nsIFrame* ptFrame = nsLayoutUtils::GetFrameForPoint(
+ RelativeTo{rootFrame}, point, GetHitTestOptions());
+ if (!ptFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ MOZ_ASSERT(fs);
+
+ nsresult result;
+ nsIFrame* newFrame = nullptr;
+ nsPoint newPoint;
+ nsPoint ptInFrame = point;
+ nsLayoutUtils::TransformPoint(RelativeTo{rootFrame}, RelativeTo{ptFrame},
+ ptInFrame);
+ result = fs->ConstrainFrameAndPointToAnchorSubtree(ptFrame, ptInFrame,
+ &newFrame, newPoint);
+ if (NS_FAILED(result) || !newFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!newFrame->IsSelectable(nullptr)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsIFrame::ContentOffsets offsets = newFrame->GetContentOffsetsFromPoint(
+ newPoint, nsIFrame::IGNORE_NATIVE_ANONYMOUS_SUBTREE);
+ if (offsets.IsNull()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (GetCaretMode() == CaretMode::Selection &&
+ !RestrictCaretDraggingOffsets(offsets)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ClearMaintainedSelection();
+
+ const nsFrameSelection::FocusMode focusMode =
+ (GetCaretMode() == CaretMode::Selection)
+ ? nsFrameSelection::FocusMode::kExtendSelection
+ : nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ fs->HandleClick(MOZ_KnownLive(offsets.content) /* bug 1636889 */,
+ offsets.StartOffset(), offsets.EndOffset(), focusMode,
+ offsets.associate);
+ return NS_OK;
+}
+
+// static
+nsRect AccessibleCaretManager::GetAllChildFrameRectsUnion(nsIFrame* aFrame) {
+ nsRect unionRect;
+
+ // Drill through scroll frames, we don't want to include scrollbar child
+ // frames below.
+ for (nsIFrame* frame = aFrame->GetContentInsertionFrame(); frame;
+ frame = frame->GetNextContinuation()) {
+ nsRect frameRect;
+
+ for (const auto& childList : frame->ChildLists()) {
+ // Loop all children to union their scrollable overflow rect.
+ for (nsIFrame* child : childList.mList) {
+ nsRect childRect = child->ScrollableOverflowRectRelativeToSelf();
+ nsLayoutUtils::TransformRect(child, frame, childRect);
+
+ // A TextFrame containing only '\n' has positive height and width 0, or
+ // positive width and height 0 if it's vertical. Need to use UnionEdges
+ // to add its rect. BRFrame rect should be non-empty.
+ if (childRect.IsEmpty()) {
+ frameRect = frameRect.UnionEdges(childRect);
+ } else {
+ frameRect = frameRect.Union(childRect);
+ }
+ }
+ }
+
+ MOZ_ASSERT(!frameRect.IsEmpty(),
+ "Editable frames should have at least one BRFrame child to make "
+ "frameRect non-empty!");
+ if (frame != aFrame) {
+ nsLayoutUtils::TransformRect(frame, aFrame, frameRect);
+ }
+ unionRect = unionRect.Union(frameRect);
+ }
+
+ return unionRect;
+}
+
+nsPoint AccessibleCaretManager::AdjustDragBoundary(
+ const nsPoint& aPoint) const {
+ nsPoint adjustedPoint = aPoint;
+
+ int32_t focusOffset = 0;
+ nsIFrame* focusFrame =
+ nsCaret::GetFrameAndOffset(GetSelection(), nullptr, 0, &focusOffset);
+ Element* editingHost = GetEditingHostForFrame(focusFrame);
+
+ if (editingHost) {
+ nsIFrame* editingHostFrame = editingHost->GetPrimaryFrame();
+ if (editingHostFrame) {
+ nsRect boundary =
+ AccessibleCaretManager::GetAllChildFrameRectsUnion(editingHostFrame);
+ nsLayoutUtils::TransformRect(editingHostFrame, mPresShell->GetRootFrame(),
+ boundary);
+
+ // Shrink the rect to make sure we never hit the boundary.
+ boundary.Deflate(kBoundaryAppUnits);
+
+ adjustedPoint = boundary.ClampPoint(adjustedPoint);
+ }
+ }
+
+ if (GetCaretMode() == CaretMode::Selection &&
+ !StaticPrefs::
+ layout_accessiblecaret_allow_dragging_across_other_caret()) {
+ // Bug 1068474: Adjust the Y-coordinate so that the carets won't be in tilt
+ // mode when a caret is being dragged surpass the other caret.
+ //
+ // For example, when dragging the second caret, the horizontal boundary
+ // (lower bound) of its Y-coordinate is the logical position of the first
+ // caret. Likewise, when dragging the first caret, the horizontal boundary
+ // (upper bound) of its Y-coordinate is the logical position of the second
+ // caret.
+ if (mActiveCaret == mCarets.GetFirst()) {
+ nscoord dragDownBoundaryY = mCarets.GetSecond()->LogicalPosition().y;
+ if (dragDownBoundaryY > 0 && adjustedPoint.y > dragDownBoundaryY) {
+ adjustedPoint.y = dragDownBoundaryY;
+ }
+ } else {
+ nscoord dragUpBoundaryY = mCarets.GetFirst()->LogicalPosition().y;
+ if (adjustedPoint.y < dragUpBoundaryY) {
+ adjustedPoint.y = dragUpBoundaryY;
+ }
+ }
+ }
+
+ return adjustedPoint;
+}
+
+void AccessibleCaretManager::StartSelectionAutoScrollTimer(
+ const nsPoint& aPoint) const {
+ Selection* selection = GetSelection();
+ MOZ_ASSERT(selection);
+
+ nsIFrame* anchorFrame = selection->GetPrimaryFrameForAnchorNode();
+ if (!anchorFrame) {
+ return;
+ }
+
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
+ anchorFrame, nsLayoutUtils::SCROLLABLE_SAME_DOC |
+ nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN);
+ if (!scrollFrame) {
+ return;
+ }
+
+ nsIFrame* capturingFrame = scrollFrame->GetScrolledFrame();
+ if (!capturingFrame) {
+ return;
+ }
+
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ MOZ_ASSERT(rootFrame);
+ nsPoint ptInScrolled = aPoint;
+ nsLayoutUtils::TransformPoint(RelativeTo{rootFrame},
+ RelativeTo{capturingFrame}, ptInScrolled);
+
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ MOZ_ASSERT(fs);
+ fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay);
+}
+
+void AccessibleCaretManager::StopSelectionAutoScrollTimer() const {
+ RefPtr<nsFrameSelection> fs = GetFrameSelection();
+ MOZ_ASSERT(fs);
+ fs->StopAutoScrollTimer();
+}
+
+void AccessibleCaretManager::DispatchCaretStateChangedEvent(
+ CaretChangedReason aReason, const nsPoint* aPoint) {
+ if (MaybeFlushLayout() == Terminated::Yes) {
+ return;
+ }
+
+ const Selection* sel = GetSelection();
+ if (!sel) {
+ return;
+ }
+
+ Document* doc = mPresShell->GetDocument();
+ MOZ_ASSERT(doc);
+
+ CaretStateChangedEventInit init;
+ init.mBubbles = true;
+
+ const nsRange* range = sel->GetAnchorFocusRange();
+ nsINode* commonAncestorNode = nullptr;
+ if (range) {
+ commonAncestorNode = range->GetClosestCommonInclusiveAncestor();
+ }
+
+ if (!commonAncestorNode) {
+ commonAncestorNode = sel->GetFrameSelection()->GetAncestorLimiter();
+ }
+
+ RefPtr<DOMRect> domRect = new DOMRect(ToSupports(doc));
+ nsRect rect = nsLayoutUtils::GetSelectionBoundingRect(sel);
+
+ nsIFrame* commonAncestorFrame = nullptr;
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+
+ if (commonAncestorNode && commonAncestorNode->IsContent()) {
+ commonAncestorFrame = commonAncestorNode->AsContent()->GetPrimaryFrame();
+ }
+
+ if (commonAncestorFrame && rootFrame) {
+ nsLayoutUtils::TransformRect(rootFrame, commonAncestorFrame, rect);
+ nsRect clampedRect =
+ nsLayoutUtils::ClampRectToScrollFrames(commonAncestorFrame, rect);
+ nsLayoutUtils::TransformRect(commonAncestorFrame, rootFrame, clampedRect);
+ rect = clampedRect;
+ init.mSelectionVisible = !clampedRect.IsEmpty();
+ } else {
+ init.mSelectionVisible = true;
+ }
+
+ domRect->SetLayoutRect(rect);
+
+ // Send isEditable info w/ event detail. This info can help determine
+ // whether to show cut command on selection dialog or not.
+ init.mSelectionEditable =
+ commonAncestorFrame && GetEditingHostForFrame(commonAncestorFrame);
+
+ init.mBoundingClientRect = domRect;
+ init.mReason = aReason;
+ init.mCollapsed = sel->IsCollapsed();
+ init.mCaretVisible = mCarets.HasLogicallyVisibleCaret();
+ init.mCaretVisuallyVisible = mCarets.HasVisuallyVisibleCaret();
+ init.mSelectedTextContent = StringifiedSelection();
+
+ if (aPoint) {
+ CSSIntPoint pt = CSSPixel::FromAppUnitsRounded(*aPoint);
+ init.mClientX = pt.x;
+ init.mClientY = pt.y;
+ }
+
+ RefPtr<CaretStateChangedEvent> event = CaretStateChangedEvent::Constructor(
+ doc, u"mozcaretstatechanged"_ns, init);
+ event->SetTrusted(true);
+
+ AC_LOG("%s: reason %" PRIu32 ", collapsed %d, caretVisible %" PRIu32,
+ __FUNCTION__, static_cast<uint32_t>(init.mReason), init.mCollapsed,
+ static_cast<uint32_t>(init.mCaretVisible));
+
+ (new AsyncEventDispatcher(doc, event.forget(), ChromeOnlyDispatch::eYes))
+ ->PostDOMEvent();
+}
+
+AccessibleCaretManager::Carets::Carets(UniquePtr<AccessibleCaret> aFirst,
+ UniquePtr<AccessibleCaret> aSecond)
+ : mFirst{std::move(aFirst)}, mSecond{std::move(aSecond)} {}
+
+} // namespace mozilla
diff --git a/layout/base/AccessibleCaretManager.h b/layout/base/AccessibleCaretManager.h
new file mode 100644
index 0000000000..036151d68a
--- /dev/null
+++ b/layout/base/AccessibleCaretManager.h
@@ -0,0 +1,442 @@
+/* -*- 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 AccessibleCaretManager_h
+#define AccessibleCaretManager_h
+
+#include "AccessibleCaret.h"
+
+#include "mozilla/Attributes.h"
+#include "mozilla/dom/CaretStateChangedEvent.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "mozilla/EnumSet.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCOMPtr.h"
+#include "nsCoord.h"
+#include "nsIFrame.h"
+#include "nsISelectionListener.h"
+
+class nsFrameSelection;
+class nsIContent;
+
+struct nsPoint;
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Element;
+class Selection;
+} // namespace dom
+
+// -----------------------------------------------------------------------------
+// AccessibleCaretManager does not deal with events or callbacks directly. It
+// relies on AccessibleCaretEventHub to call its public methods to do the work.
+// All codes needed to interact with PresShell, Selection, and AccessibleCaret
+// should be written in AccessibleCaretManager.
+//
+// None the public methods in AccessibleCaretManager will flush layout or style
+// prior to performing its task. The caller must ensure the layout is up to
+// date.
+// TODO: it's unclear, whether that's true. `OnSelectionChanged` calls
+// `UpdateCarets`, which may flush layout.
+//
+// Please see the wiki page for more information.
+// https://wiki.mozilla.org/AccessibleCaret
+//
+class AccessibleCaretManager {
+ public:
+ // @param aPresShell may be nullptr for testing.
+ explicit AccessibleCaretManager(PresShell* aPresShell);
+ virtual ~AccessibleCaretManager() = default;
+
+ // Called by AccessibleCaretEventHub to inform us that PresShell is destroyed.
+ void Terminate();
+
+ // The aPoint in the following public methods should be relative to root
+ // frame.
+
+ // Press caret on the given point. Return NS_OK if the point is actually on
+ // one of the carets.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult PressCaret(const nsPoint& aPoint, EventClassID aEventClass);
+
+ // Drag caret to the given point. It's required to call PressCaret()
+ // beforehand.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult DragCaret(const nsPoint& aPoint);
+
+ // Release caret from he previous press action. It's required to call
+ // PressCaret() beforehand.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult ReleaseCaret();
+
+ // A quick single tap on caret on given point without dragging.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult TapCaret(const nsPoint& aPoint);
+
+ // Select a word or bring up paste shortcut (if Gaia is listening) under the
+ // given point.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult SelectWordOrShortcut(const nsPoint& aPoint);
+
+ // Handle scroll-start event.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollStart();
+
+ // Handle scroll-end event.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollEnd();
+
+ // Handle ScrollPositionChanged from nsIScrollObserver. This might be called
+ // at anytime, not necessary between OnScrollStart and OnScrollEnd.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnScrollPositionChanged();
+
+ // Handle reflow event from nsIReflowObserver.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnReflow();
+
+ // Handle blur event from nsFocusManager.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnBlur();
+
+ // Handle NotifySelectionChanged event from nsISelectionListener.
+ // @param aReason potentially multiple of the reasons defined in
+ // nsISelectionListener.idl.
+ MOZ_CAN_RUN_SCRIPT
+ virtual nsresult OnSelectionChanged(dom::Document* aDoc, dom::Selection* aSel,
+ int16_t aReason);
+ // Handle key event.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void OnKeyboardEvent();
+
+ // Update the manager with the last input source that was observed. This
+ // is used in part to determine if the carets should be shown or hidden.
+ void SetLastInputSource(uint16_t aInputSource);
+
+ // Returns True indicating that we should disable APZ to avoid jumpy carets.
+ bool ShouldDisableApz() const;
+
+ protected:
+ class Carets;
+
+ // @param aPresShell may be nullptr for testing.
+ AccessibleCaretManager(PresShell* aPresShell, Carets aCarets);
+
+ // This enum representing the number of AccessibleCarets on the screen.
+ enum class CaretMode : uint8_t {
+ // No caret on the screen.
+ None,
+
+ // One caret, i.e. the selection is collapsed.
+ Cursor,
+
+ // Two carets, i.e. the selection is not collapsed.
+ Selection
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const CaretMode& aCaretMode);
+
+ enum class UpdateCaretsHint : uint8_t {
+ // Update everything including appearance and position.
+ Default,
+
+ // Update everything while respecting the old appearance. For example, if
+ // the caret in cursor mode is hidden due to blur, do not change its
+ // appearance to Normal.
+ RespectOldAppearance,
+
+ // No CaretStateChangedEvent will be dispatched in the end of
+ // UpdateCarets().
+ DispatchNoEvent,
+ };
+
+ using UpdateCaretsHintSet = mozilla::EnumSet<UpdateCaretsHint>;
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const UpdateCaretsHint& aResult);
+
+ enum class Terminated : bool { No, Yes };
+
+ // This method could kill the shell, so callers to methods that call
+ // MaybeFlushLayout should ensure the event hub that owns us is still alive.
+ //
+ // See the mRefCnt assertions in AccessibleCaretEventHub.
+ //
+ [[nodiscard]] MOZ_CAN_RUN_SCRIPT virtual Terminated MaybeFlushLayout();
+
+ // Update carets based on current selection status. This function will flush
+ // layout, so caller must ensure the PresShell is still valid after calling
+ // this method.
+ MOZ_CAN_RUN_SCRIPT
+ void UpdateCarets(
+ const UpdateCaretsHintSet& aHints = UpdateCaretsHint::Default);
+
+ // Force hiding all carets regardless of the current selection status, and
+ // dispatch CaretStateChangedEvent if one of the carets is logically-visible.
+ MOZ_CAN_RUN_SCRIPT
+ void HideCaretsAndDispatchCaretStateChangedEvent();
+
+ MOZ_CAN_RUN_SCRIPT
+ void UpdateCaretsForCursorMode(const UpdateCaretsHintSet& aHints);
+
+ MOZ_CAN_RUN_SCRIPT
+ void UpdateCaretsForSelectionMode(const UpdateCaretsHintSet& aHints);
+
+ // A helper function to update mShouldDisableApz.
+ void UpdateShouldDisableApz();
+
+ // Provide haptic / touch feedback, primarily for select on longpress.
+ void ProvideHapticFeedback();
+
+ // Get the nearest enclosing focusable frame of aFrame.
+ // @return focusable frame if there is any; nullptr otherwise.
+ nsIFrame* GetFocusableFrame(nsIFrame* aFrame) const;
+
+ // Change focus to aFrame if it isn't nullptr. Otherwise, clear the old focus
+ // then re-focus the window.
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void ChangeFocusToOrClearOldFocus(
+ nsIFrame* aFrame) const;
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult SelectWord(nsIFrame* aFrame, const nsPoint& aPoint) const;
+ MOZ_CAN_RUN_SCRIPT void SetSelectionDragState(bool aState) const;
+
+ // Return true if the candidate string is a phone number.
+ bool IsPhoneNumber(const nsAString& aCandidate) const;
+
+ // Extend the current selection forwards and backwards if it's already a
+ // phone number.
+ MOZ_CAN_RUN_SCRIPT
+ void SelectMoreIfPhoneNumber() const;
+
+ // Extend the current phone number selection in the requested direction.
+ MOZ_CAN_RUN_SCRIPT
+ void ExtendPhoneNumberSelection(const nsAString& aDirection) const;
+
+ void SetSelectionDirection(nsDirection aDir) const;
+
+ // If aDirection is eDirNext, get the frame for the range start in the first
+ // range from the current selection, and return the offset into that frame as
+ // well as the range start content and the content offset. Otherwise, get the
+ // frame and the offset for the range end in the last range instead.
+ nsIFrame* GetFrameForFirstRangeStartOrLastRangeEnd(
+ nsDirection aDirection, int32_t* aOutOffset,
+ nsIContent** aOutContent = nullptr,
+ int32_t* aOutContentOffset = nullptr) const;
+
+ MOZ_CAN_RUN_SCRIPT nsresult DragCaretInternal(const nsPoint& aPoint);
+ nsPoint AdjustDragBoundary(const nsPoint& aPoint) const;
+
+ // Start the selection scroll timer if the caret is being dragged out of
+ // the scroll port.
+ MOZ_CAN_RUN_SCRIPT
+ void StartSelectionAutoScrollTimer(const nsPoint& aPoint) const;
+ void StopSelectionAutoScrollTimer() const;
+
+ void ClearMaintainedSelection() const;
+
+ static dom::Element* GetEditingHostForFrame(const nsIFrame* aFrame);
+ dom::Selection* GetSelection() const;
+ already_AddRefed<nsFrameSelection> GetFrameSelection() const;
+
+ MOZ_CAN_RUN_SCRIPT
+ nsAutoString StringifiedSelection() const;
+
+ // Get the union of all the child frame scrollable overflow rects for aFrame,
+ // which is used as a helper function to restrict the area where the caret can
+ // be dragged. Returns the rect relative to aFrame.
+ static nsRect GetAllChildFrameRectsUnion(nsIFrame* aFrame);
+
+ // Restrict the active caret's dragging position based on
+ // sCaretsAllowDraggingAcrossOtherCaret. If the active caret is the first
+ // caret, the `limit` will be the previous character of the second caret.
+ // Otherwise, the `limit` will be the next character of the first caret.
+ //
+ // @param aOffsets is the new position of the active caret, and it will be set
+ // to the `limit` when 1) sCaretsAllowDraggingAcrossOtherCaret is false and
+ // it's being dragged past the limit. 2) sCaretsAllowDraggingAcrossOtherCaret
+ // is true and the active caret's position is the same as the inactive's
+ // position.
+ // @return true if the aOffsets is suitable for changing the selection.
+ bool RestrictCaretDraggingOffsets(nsIFrame::ContentOffsets& aOffsets);
+
+ // ---------------------------------------------------------------------------
+ // The following functions are made virtual for stubbing or mocking in gtest.
+ //
+ // @return Yes if Terminate() had been called.
+ virtual Terminated IsTerminated() const {
+ return mPresShell ? Terminated::No : Terminated::Yes;
+ }
+
+ // Get caret mode based on current selection.
+ virtual CaretMode GetCaretMode() const;
+
+ // @return true if aStartFrame comes before aEndFrame.
+ virtual bool CompareTreePosition(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame) const;
+
+ // Check if the two carets is overlapping to become tilt.
+ // @return true if the two carets become tilt; false, otherwise.
+ virtual bool UpdateCaretsForOverlappingTilt();
+
+ // Make the two carets always tilt.
+ virtual void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame,
+ const nsIFrame* aEndFrame);
+
+ // Check whether AccessibleCaret is displayable in cursor mode or not.
+ // @param aOutFrame returns frame of the cursor if it's displayable.
+ // @param aOutOffset returns frame offset as well.
+ virtual bool IsCaretDisplayableInCursorMode(
+ nsIFrame** aOutFrame = nullptr, int32_t* aOutOffset = nullptr) const;
+
+ virtual bool HasNonEmptyTextContent(nsINode* aNode) const;
+
+ // This function will flush layout, so caller must ensure the PresShell is
+ // still valid after calling this method.
+ // @param aPoint The event point when the user is pressing or dragging a
+ // caret, which is relative to the root frame.
+ MOZ_CAN_RUN_SCRIPT
+ virtual void DispatchCaretStateChangedEvent(dom::CaretChangedReason aReason,
+ const nsPoint* aPoint = nullptr);
+
+ // ---------------------------------------------------------------------------
+ // Member variables
+ //
+ nscoord mOffsetYToCaretLogicalPosition = NS_UNCONSTRAINEDSIZE;
+
+ // AccessibleCaretEventHub owns us by a UniquePtr. When it's destroyed, we'll
+ // also be destroyed. No need to worry if we outlive mPresShell.
+ //
+ // mPresShell will be set to nullptr in Terminate(). Therefore mPresShell is
+ // nullptr either we are in gtest or PresShell::IsDestroying() is true.
+ PresShell* MOZ_NON_OWNING_REF mPresShell = nullptr;
+
+ class Carets {
+ public:
+ Carets(UniquePtr<AccessibleCaret> aFirst,
+ UniquePtr<AccessibleCaret> aSecond);
+
+ Carets(Carets&&) = default;
+ Carets(const Carets&) = delete;
+ Carets& operator=(const Carets&) = delete;
+
+ AccessibleCaret* GetFirst() const { return mFirst.get(); }
+
+ AccessibleCaret* GetSecond() const { return mSecond.get(); }
+
+ bool HasLogicallyVisibleCaret() const {
+ return mFirst->IsLogicallyVisible() || mSecond->IsLogicallyVisible();
+ }
+
+ bool HasVisuallyVisibleCaret() const {
+ return mFirst->IsVisuallyVisible() || mSecond->IsVisuallyVisible();
+ }
+
+ void Terminate() {
+ mFirst = nullptr;
+ mSecond = nullptr;
+ }
+
+ private:
+ // First caret is attached to nsCaret in cursor mode, and is attached to
+ // selection highlight as the left caret in selection mode.
+ UniquePtr<AccessibleCaret> mFirst;
+
+ // Second caret is used solely in selection mode, and is attached to
+ // selection highlight as the right caret.
+ UniquePtr<AccessibleCaret> mSecond;
+ };
+
+ Carets mCarets;
+
+ // The caret being pressed or dragged.
+ AccessibleCaret* mActiveCaret = nullptr;
+
+ // The caret mode since last update carets.
+ CaretMode mLastUpdateCaretMode = CaretMode::None;
+
+ // The last input source that the event hub saw. We use this to decide whether
+ // or not show the carets when the selection is updated, as we want to hide
+ // the carets for mouse-triggered selection changes but show them for other
+ // input types such as touch.
+ uint16_t mLastInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
+
+ // Set to true in OnScrollStart() and set to false in OnScrollEnd().
+ bool mIsScrollStarted = false;
+
+ class LayoutFlusher final {
+ public:
+ LayoutFlusher() = default;
+
+ ~LayoutFlusher();
+
+ LayoutFlusher(const LayoutFlusher&) = delete;
+ LayoutFlusher& operator=(const LayoutFlusher&) = delete;
+
+ MOZ_CAN_RUN_SCRIPT void MaybeFlush(const PresShell& aPresShell);
+
+ // Set to false to disallow flushing layout in some callbacks such as
+ // OnReflow(), OnScrollStart(), OnScrollStart(), or
+ // OnScrollPositionChanged().
+ bool mAllowFlushing = true;
+
+ private:
+ // Whether we're flushing layout, used for sanity-checking.
+ bool mFlushing = false;
+ };
+
+ LayoutFlusher mLayoutFlusher;
+
+ // Set to True if one of the caret's position is changed in last update.
+ bool mIsCaretPositionChanged = false;
+
+ class DesiredAsyncPanZoomState final {
+ public:
+ void Update(const AccessibleCaretManager& aAccessibleCaretManager);
+
+ enum class Value : bool { Disabled, Enabled };
+
+ Value Get() const { return mValue; }
+
+ private:
+ Value mValue = Value::Enabled;
+ };
+
+ DesiredAsyncPanZoomState mDesiredAsyncPanZoomState;
+
+ static const int32_t kAutoScrollTimerDelay = 30;
+
+ // Clicking on the boundary of input or textarea will move the caret to the
+ // front or end of the content. To avoid this, we need to deflate the content
+ // boundary by 61 app units, which is 1 pixel + 1 app unit as defined in
+ // AppUnit.h.
+ static const int32_t kBoundaryAppUnits = 61;
+
+ enum ScriptUpdateMode : int32_t {
+ // By default, always hide carets for selection changes due to JS calls.
+ kScriptAlwaysHide,
+ // Update any visible carets for selection changes due to JS calls,
+ // but don't show carets if carets are hidden.
+ kScriptUpdateVisible,
+ // Always show carets for selection changes due to JS calls.
+ kScriptAlwaysShow
+ };
+};
+
+std::ostream& operator<<(std::ostream& aStream,
+ const AccessibleCaretManager::CaretMode& aCaretMode);
+
+std::ostream& operator<<(
+ std::ostream& aStream,
+ const AccessibleCaretManager::UpdateCaretsHint& aResult);
+
+} // namespace mozilla
+
+#endif // AccessibleCaretManager_h
diff --git a/layout/base/ArenaObjectID.h b/layout/base/ArenaObjectID.h
new file mode 100644
index 0000000000..6177afcc2d
--- /dev/null
+++ b/layout/base/ArenaObjectID.h
@@ -0,0 +1,25 @@
+/* -*- 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/. */
+
+/* enum type for objects that can be allocated by an nsPresArena */
+
+#ifndef mozilla_ArenaObjectID_h
+#define mozilla_ArenaObjectID_h
+
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+
+enum ArenaObjectID {
+#define PRES_ARENA_OBJECT(name_) eArenaObjectID_##name_,
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT
+ eArenaObjectID_COUNT
+};
+
+}; // namespace mozilla
+
+#endif
diff --git a/layout/base/AutoProfilerStyleMarker.h b/layout/base/AutoProfilerStyleMarker.h
new file mode 100644
index 0000000000..938ec635c2
--- /dev/null
+++ b/layout/base/AutoProfilerStyleMarker.h
@@ -0,0 +1,95 @@
+/* -*- 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_AutoProfilerStyleMarker_h
+#define mozilla_AutoProfilerStyleMarker_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ServoTraversalStatistics.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+class MOZ_RAII AutoProfilerStyleMarker {
+ public:
+ explicit AutoProfilerStyleMarker(UniquePtr<ProfileChunkedBuffer> aCause,
+ const Maybe<uint64_t>& aInnerWindowID)
+ : mActive(profiler_thread_is_being_profiled_for_markers()),
+ mCause(std::move(aCause)),
+ mInnerWindowID(aInnerWindowID) {
+ if (!mActive) {
+ return;
+ }
+ MOZ_ASSERT(!ServoTraversalStatistics::sActive,
+ "Nested AutoProfilerStyleMarker");
+ ServoTraversalStatistics::sSingleton = ServoTraversalStatistics();
+ ServoTraversalStatistics::sActive = true;
+
+ mStartTime = TimeStamp::Now();
+ }
+
+ ~AutoProfilerStyleMarker() {
+ if (!mActive) {
+ return;
+ }
+
+ struct StyleMarker {
+ static constexpr mozilla::Span<const char> MarkerTypeName() {
+ return mozilla::MakeStringSpan("Styles");
+ }
+ static void StreamJSONMarkerData(
+ baseprofiler::SpliceableJSONWriter& aWriter,
+ uint32_t aElementsTraversed, uint32_t aElementsStyled,
+ uint32_t aElementsMatched, uint32_t aStylesShared,
+ uint32_t aStylesReused) {
+ aWriter.IntProperty("elementsTraversed", aElementsTraversed);
+ aWriter.IntProperty("elementsStyled", aElementsStyled);
+ aWriter.IntProperty("elementsMatched", aElementsMatched);
+ aWriter.IntProperty("stylesShared", aStylesShared);
+ aWriter.IntProperty("stylesReused", aStylesReused);
+ }
+ static MarkerSchema MarkerTypeDisplay() {
+ using MS = MarkerSchema;
+ MS schema{MS::Location::MarkerChart, MS::Location::MarkerTable,
+ MS::Location::TimelineOverview};
+ schema.AddKeyLabelFormat("elementsTraversed", "Elements traversed",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("elementsStyled", "Elements styled",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("elementsMatched", "Elements matched",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("stylesShared", "Styles shared",
+ MS::Format::Integer);
+ schema.AddKeyLabelFormat("stylesReused", "Styles reused",
+ MS::Format::Integer);
+ return schema;
+ }
+ };
+
+ ServoTraversalStatistics::sActive = false;
+ profiler_add_marker("Styles", geckoprofiler::category::LAYOUT,
+ {MarkerTiming::IntervalUntilNowFrom(mStartTime),
+ MarkerStack::TakeBacktrace(std::move(mCause)),
+ MarkerInnerWindowId(mInnerWindowID)},
+ StyleMarker{},
+ ServoTraversalStatistics::sSingleton.mElementsTraversed,
+ ServoTraversalStatistics::sSingleton.mElementsStyled,
+ ServoTraversalStatistics::sSingleton.mElementsMatched,
+ ServoTraversalStatistics::sSingleton.mStylesShared,
+ ServoTraversalStatistics::sSingleton.mStylesReused);
+ }
+
+ private:
+ bool mActive;
+ TimeStamp mStartTime;
+ UniquePtr<ProfileChunkedBuffer> mCause;
+ Maybe<uint64_t> mInnerWindowID;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_AutoProfilerStyleMarker_h
diff --git a/layout/base/Baseline.cpp b/layout/base/Baseline.cpp
new file mode 100644
index 0000000000..72118400f9
--- /dev/null
+++ b/layout/base/Baseline.cpp
@@ -0,0 +1,105 @@
+/* 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 "Baseline.h"
+#include "nsIFrame.h"
+
+namespace mozilla {
+
+nscoord Baseline::SynthesizeBOffsetFromMarginBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup aGroup) {
+ MOZ_ASSERT(!aWM.IsOrthogonalTo(aFrame->GetWritingMode()));
+ auto margin = aFrame->GetLogicalUsedMargin(aWM);
+ if (aGroup == BaselineSharingGroup::First) {
+ if (aWM.IsAlphabeticalBaseline()) {
+ // First baseline for inverted-line content is the block-start margin
+ // edge, as the frame is in effect "flipped" for alignment purposes.
+ return MOZ_UNLIKELY(aWM.IsLineInverted())
+ ? -margin.BStart(aWM)
+ : aFrame->BSize(aWM) + margin.BEnd(aWM);
+ }
+ nscoord marginBoxCenter = (aFrame->BSize(aWM) + margin.BStartEnd(aWM)) / 2;
+ return marginBoxCenter - margin.BStart(aWM);
+ }
+ MOZ_ASSERT(aGroup == BaselineSharingGroup::Last);
+ if (aWM.IsAlphabeticalBaseline()) {
+ // Last baseline for inverted-line content is the block-start margin edge,
+ // as the frame is in effect "flipped" for alignment purposes.
+ return MOZ_UNLIKELY(aWM.IsLineInverted())
+ ? aFrame->BSize(aWM) + margin.BStart(aWM)
+ : -margin.BEnd(aWM);
+ }
+ // Round up for central baseline offset, to be consistent with ::First.
+ nscoord marginBoxSize = aFrame->BSize(aWM) + margin.BStartEnd(aWM);
+ nscoord marginBoxCenter = (marginBoxSize / 2) + (marginBoxSize % 2);
+ return marginBoxCenter - margin.BEnd(aWM);
+}
+
+enum class BoxType { Border, Padding, Content };
+
+template <BoxType aType>
+static nscoord SynthesizeBOffsetFromInnerBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup aGroup) {
+ WritingMode wm = aFrame->GetWritingMode();
+ MOZ_ASSERT_IF(aType != BoxType::Border, !aWM.IsOrthogonalTo(wm));
+ const nscoord borderBoxSize = MOZ_UNLIKELY(aWM.IsOrthogonalTo(wm))
+ ? aFrame->ISize(aWM)
+ : aFrame->BSize(aWM);
+ const LogicalMargin bp = ([&] {
+ switch (aType) {
+ case BoxType::Border:
+ return LogicalMargin(aWM);
+ case BoxType::Padding:
+ return aFrame->GetLogicalUsedBorder(wm)
+ .ApplySkipSides(aFrame->GetLogicalSkipSides())
+ .ConvertTo(aWM, wm);
+ case BoxType::Content:
+ return aFrame->GetLogicalUsedBorderAndPadding(wm)
+ .ApplySkipSides(aFrame->GetLogicalSkipSides())
+ .ConvertTo(aWM, wm);
+ }
+ MOZ_CRASH();
+ })();
+ if (MOZ_UNLIKELY(aWM.IsCentralBaseline())) {
+ nscoord boxBSize = borderBoxSize - bp.BStartEnd(aWM);
+ if (aGroup == BaselineSharingGroup::First) {
+ return boxBSize / 2 + bp.BStart(aWM);
+ }
+ // Return the same center position as for ::First, but as offset from end:
+ nscoord halfBoxBSize = (boxBSize / 2) + (boxBSize % 2);
+ return halfBoxBSize + bp.BEnd(aWM);
+ }
+ if (aGroup == BaselineSharingGroup::First) {
+ // First baseline for inverted-line content is the block-start content
+ // edge, as the frame is in effect "flipped" for alignment purposes.
+ return MOZ_UNLIKELY(aWM.IsLineInverted()) ? bp.BStart(aWM)
+ : borderBoxSize - bp.BEnd(aWM);
+ }
+ // Last baseline for inverted-line content is the block-start content edge,
+ // as the frame is in effect "flipped" for alignment purposes.
+ return MOZ_UNLIKELY(aWM.IsLineInverted()) ? borderBoxSize - bp.BStart(aWM)
+ : bp.BEnd(aWM);
+}
+
+nscoord Baseline::SynthesizeBOffsetFromContentBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup aGroup) {
+ return SynthesizeBOffsetFromInnerBox<BoxType::Content>(aFrame, aWM, aGroup);
+}
+
+nscoord Baseline::SynthesizeBOffsetFromPaddingBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup aGroup) {
+ return SynthesizeBOffsetFromInnerBox<BoxType::Padding>(aFrame, aWM, aGroup);
+}
+
+nscoord Baseline::SynthesizeBOffsetFromBorderBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup aGroup) {
+ return SynthesizeBOffsetFromInnerBox<BoxType::Border>(aFrame, aWM, aGroup);
+}
+
+} // namespace mozilla
diff --git a/layout/base/Baseline.h b/layout/base/Baseline.h
new file mode 100644
index 0000000000..384ed0d591
--- /dev/null
+++ b/layout/base/Baseline.h
@@ -0,0 +1,78 @@
+/* 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_BASE_BASELINE_H_
+#define LAYOUT_BASE_BASELINE_H_
+
+#include "nsCoord.h"
+#include "mozilla/WritingModes.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+// https://drafts.csswg.org/css-align-3/#baseline-sharing-group
+enum class BaselineSharingGroup : uint8_t {
+ // NOTE Used as an array index so must be 0 and 1.
+ First = 0,
+ Last = 1,
+};
+
+// Layout context under which the baseline is being exported to.
+enum class BaselineExportContext : uint8_t {
+ LineLayout = 0,
+ Other = 1,
+};
+
+class Baseline {
+ public:
+ /**
+ * Synthesize a first(last) inline-axis baseline in aWM based on aFrame's
+ * margin-box.
+ *
+ * An alphabetical baseline is at the end edge of aFrame's margin-box with
+ * respect to aWM's block-axis, and a central baseline is halfway between the
+ * start and end edges. (aWM tells which baseline to use.)
+ * https://drafts.csswg.org/css-align-3/#synthesize-baseline
+ *
+ * @note This works only when aFrame's writing-mode is parallel to aWM.
+ * @param aWM the writing-mode of the alignment context.
+ * @return an offset from aFrame's border-box start(end) edge in aWM's
+ * block-axis for a first(last) baseline, respectively.
+ */
+ static nscoord SynthesizeBOffsetFromMarginBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup);
+
+ /**
+ * Synthesize a first(last) inline-axis baseline in aWM based on aFrame's
+ * border-box.
+ *
+ * An alphabetical baseline is at the end edge of aFrame's border-box with
+ * respect to aWM's block-axis, and a central baseline is halfway between the
+ * start and end edges. (aWM tells which baseline to use.)
+ * https://drafts.csswg.org/css-align-3/#synthesize-baseline
+ *
+ * @param aWM the writing-mode of the alignment context.
+ * @return an offset from aFrame's border-box start(end) edge in aWM's
+ * block-axis for a first(last) baseline, respectively.
+ */
+ static nscoord SynthesizeBOffsetFromBorderBox(const nsIFrame* aFrame,
+ WritingMode aWM,
+ BaselineSharingGroup);
+ /**
+ * As above, but using the content box.
+ */
+ static nscoord SynthesizeBOffsetFromContentBox(const nsIFrame*, WritingMode,
+ BaselineSharingGroup);
+ /**
+ * As above, but using the padding box.
+ */
+ static nscoord SynthesizeBOffsetFromPaddingBox(const nsIFrame*, WritingMode,
+ BaselineSharingGroup);
+};
+
+} // namespace mozilla
+
+#endif // LAYOUT_BASE_BASELINE_H_
diff --git a/layout/base/CaretAssociationHint.h b/layout/base/CaretAssociationHint.h
new file mode 100644
index 0000000000..5bfab7a879
--- /dev/null
+++ b/layout/base/CaretAssociationHint.h
@@ -0,0 +1,21 @@
+/* -*- 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_CaretAssociationHint_h
+#define mozilla_CaretAssociationHint_h
+
+namespace mozilla {
+
+/**
+ * Hint whether a caret is associated with the content before a
+ * given character offset (Before), or with the content after a given
+ * character offset (After).
+ */
+enum class CaretAssociationHint { Before, After };
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ContainStyleScopeManager.cpp b/layout/base/ContainStyleScopeManager.cpp
new file mode 100644
index 0000000000..80f3d79013
--- /dev/null
+++ b/layout/base/ContainStyleScopeManager.cpp
@@ -0,0 +1,242 @@
+/* -*- 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 "ContainStyleScopeManager.h"
+
+#include "mozilla/ServoStyleSet.h"
+#include "nsIContentInlines.h"
+#include "CounterStyleManager.h"
+#include "nsCounterManager.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsContentUtils.h"
+#include "nsQuoteList.h"
+
+namespace mozilla {
+
+nsGenConNode* ContainStyleScope::GetPrecedingElementInGenConList(
+ nsGenConList* aList) {
+ auto IsAfter = [this](nsGenConNode* aNode) {
+ return nsContentUtils::CompareTreePosition<TreeKind::Flat>(
+ mContent, aNode->mPseudoFrame->GetContent(),
+ /* aCommonAncestor = */ nullptr) > 0;
+ };
+ return aList->BinarySearch(IsAfter);
+}
+
+void ContainStyleScope::RecalcAllCounters() {
+ GetCounterManager().RecalcAll();
+ for (auto* child : mChildren) {
+ child->RecalcAllCounters();
+ }
+}
+
+void ContainStyleScope::RecalcAllQuotes() {
+ GetQuoteList().RecalcAll();
+ for (auto* child : mChildren) {
+ child->RecalcAllQuotes();
+ }
+}
+
+ContainStyleScope& ContainStyleScopeManager::GetOrCreateScopeForContent(
+ nsIContent* aContent) {
+ for (; aContent; aContent = aContent->GetFlattenedTreeParent()) {
+ auto* element = dom::Element::FromNode(*aContent);
+ if (!element) {
+ continue;
+ }
+
+ // Do not allow elements which have `display: contents` to create style
+ // boundaries. See https://github.com/w3c/csswg-drafts/issues/7392.
+ if (element->IsDisplayContents()) {
+ continue;
+ }
+
+ const auto* style = Servo_Element_GetMaybeOutOfDateStyle(element);
+ if (!style) {
+ continue;
+ }
+
+ if (!style->SelfOrAncestorHasContainStyle()) {
+ return GetRootScope();
+ }
+
+ if (!style->StyleDisplay()->IsContainStyle()) {
+ continue;
+ }
+
+ if (auto* scope = mScopes.Get(aContent)) {
+ return *scope;
+ }
+
+ auto& parentScope =
+ GetOrCreateScopeForContent(aContent->GetFlattenedTreeParent());
+ return *mScopes.InsertOrUpdate(
+ aContent, MakeUnique<ContainStyleScope>(this, &parentScope, aContent));
+ }
+
+ return GetRootScope();
+}
+
+ContainStyleScope& ContainStyleScopeManager::GetScopeForContent(
+ nsIContent* aContent) {
+ MOZ_ASSERT(aContent);
+
+ if (auto* element = dom::Element::FromNode(*aContent)) {
+ if (const auto* style = Servo_Element_GetMaybeOutOfDateStyle(element)) {
+ if (!style->SelfOrAncestorHasContainStyle()) {
+ return GetRootScope();
+ }
+ }
+ }
+
+ for (; aContent; aContent = aContent->GetFlattenedTreeParent()) {
+ if (auto* scope = mScopes.Get(aContent)) {
+ return *scope;
+ }
+ }
+
+ return GetRootScope();
+}
+
+void ContainStyleScopeManager::Clear() {
+ GetRootScope().GetQuoteList().Clear();
+ GetRootScope().GetCounterManager().Clear();
+
+ DestroyScope(&GetRootScope());
+ MOZ_DIAGNOSTIC_ASSERT(mScopes.IsEmpty(),
+ "Destroying the root scope should destroy all scopes.");
+}
+
+void ContainStyleScopeManager::DestroyScopesFor(nsIFrame* aFrame) {
+ if (auto* scope = mScopes.Get(aFrame->GetContent())) {
+ DestroyScope(scope);
+ }
+}
+
+void ContainStyleScopeManager::DestroyScope(ContainStyleScope* aScope) {
+ // Deleting a scope modifies the array of children in its parent, so we don't
+ // use an iterator here.
+ while (!aScope->GetChildren().IsEmpty()) {
+ DestroyScope(aScope->GetChildren().ElementAt(0));
+ }
+ mScopes.Remove(aScope->GetContent());
+}
+
+bool ContainStyleScopeManager::DestroyCounterNodesFor(nsIFrame* aFrame) {
+ bool result = false;
+ for (auto* scope = &GetScopeForContent(aFrame->GetContent()); scope;
+ scope = scope->GetParent()) {
+ result |= scope->GetCounterManager().DestroyNodesFor(aFrame);
+ }
+ return result;
+}
+
+bool ContainStyleScopeManager::AddCounterChanges(nsIFrame* aNewFrame) {
+ return GetOrCreateScopeForContent(
+ aNewFrame->GetContent()->GetFlattenedTreeParent())
+ .GetCounterManager()
+ .AddCounterChanges(aNewFrame);
+}
+
+nsCounterList* ContainStyleScopeManager::GetOrCreateCounterList(
+ dom::Element& aElement, nsAtom* aCounterName) {
+ return GetOrCreateScopeForContent(&aElement)
+ .GetCounterManager()
+ .GetOrCreateCounterList(aCounterName);
+}
+
+bool ContainStyleScopeManager::CounterDirty(nsAtom* aCounterName) {
+ return mDirtyCounters.Contains(aCounterName);
+}
+
+void ContainStyleScopeManager::SetCounterDirty(nsAtom* aCounterName) {
+ mDirtyCounters.Insert(aCounterName);
+}
+
+void ContainStyleScopeManager::RecalcAllCounters() {
+ GetRootScope().RecalcAllCounters();
+ mDirtyCounters.Clear();
+}
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+void ContainStyleScopeManager::DumpCounters() {
+ GetRootScope().GetCounterManager().Dump();
+ for (auto& entry : mScopes) {
+ entry.GetWeak()->GetCounterManager().Dump();
+ }
+}
+#endif
+
+#ifdef ACCESSIBILITY
+static bool GetFirstCounterValueForScopeAndFrame(ContainStyleScope* aScope,
+ nsIFrame* aFrame,
+ CounterValue& aOrdinal) {
+ if (aScope->GetCounterManager().GetFirstCounterValueForFrame(aFrame,
+ aOrdinal)) {
+ return true;
+ }
+ for (auto* child : aScope->GetChildren()) {
+ if (GetFirstCounterValueForScopeAndFrame(child, aFrame, aOrdinal)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void ContainStyleScopeManager::GetSpokenCounterText(nsIFrame* aFrame,
+ nsAString& aText) {
+ CounterValue ordinal = 1;
+ GetFirstCounterValueForScopeAndFrame(&GetRootScope(), aFrame, ordinal);
+
+ CounterStyle* counterStyle =
+ aFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle(
+ aFrame->StyleList()->mCounterStyle);
+ nsAutoString text;
+ bool isBullet;
+ counterStyle->GetSpokenCounterText(ordinal, aFrame->GetWritingMode(), text,
+ isBullet);
+ if (isBullet) {
+ aText = text;
+ if (!counterStyle->IsNone()) {
+ aText.Append(' ');
+ }
+ } else {
+ counterStyle->GetPrefix(aText);
+ aText += text;
+ nsAutoString suffix;
+ counterStyle->GetSuffix(suffix);
+ aText += suffix;
+ }
+}
+#endif
+
+void ContainStyleScopeManager::SetAllCountersDirty() {
+ GetRootScope().GetCounterManager().SetAllDirty();
+ for (auto& entry : mScopes) {
+ entry.GetWeak()->GetCounterManager().SetAllDirty();
+ }
+}
+
+bool ContainStyleScopeManager::DestroyQuoteNodesFor(nsIFrame* aFrame) {
+ bool result = false;
+ for (auto* scope = &GetScopeForContent(aFrame->GetContent()); scope;
+ scope = scope->GetParent()) {
+ result |= scope->GetQuoteList().DestroyNodesFor(aFrame);
+ }
+ return result;
+}
+
+nsQuoteList* ContainStyleScopeManager::QuoteListFor(dom::Element& aElement) {
+ return &GetOrCreateScopeForContent(&aElement).GetQuoteList();
+}
+
+void ContainStyleScopeManager::RecalcAllQuotes() {
+ GetRootScope().RecalcAllQuotes();
+}
+
+} // namespace mozilla
diff --git a/layout/base/ContainStyleScopeManager.h b/layout/base/ContainStyleScopeManager.h
new file mode 100644
index 0000000000..890344c93e
--- /dev/null
+++ b/layout/base/ContainStyleScopeManager.h
@@ -0,0 +1,139 @@
+/* -*- 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 ContainStyleScopeManager_h_
+#define ContainStyleScopeManager_h_
+
+#include "nsClassHashtable.h"
+#include "nsTHashSet.h"
+#include "nsQuoteList.h"
+#include "nsCounterManager.h"
+#include <memory>
+
+class nsIContent;
+class nsAtom;
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+class ContainStyleScopeManager;
+
+/* Implementation of a self-contained `contain: style` scope which manages its
+ * own counters and quotes. Since the `counters()` function has read access to
+ * other `contain: style` scopes, USE counter nodes may link across `contain:
+ * style` scopes. */
+class ContainStyleScope final {
+ public:
+ ContainStyleScope(ContainStyleScopeManager* aManager,
+ ContainStyleScope* aParent, nsIContent* aContent)
+ : mQuoteList(this),
+ mCounterManager(this),
+ mScopeManager(aManager),
+ mParent(aParent),
+ mContent(aContent) {
+ MOZ_ASSERT(aManager);
+ if (mParent) {
+ mParent->AddChild(this);
+ }
+ }
+
+ ~ContainStyleScope() {
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ }
+
+ nsQuoteList& GetQuoteList() { return mQuoteList; }
+ nsCounterManager& GetCounterManager() { return mCounterManager; }
+ ContainStyleScopeManager& GetScopeManager() { return *mScopeManager; }
+ ContainStyleScope* GetParent() { return mParent; }
+ nsIContent* GetContent() { return mContent; }
+
+ void AddChild(ContainStyleScope* aScope) { mChildren.AppendElement(aScope); }
+ void RemoveChild(ContainStyleScope* aScope) {
+ mChildren.RemoveElement(aScope);
+ }
+ const nsTArray<ContainStyleScope*>& GetChildren() const { return mChildren; }
+
+ void RecalcAllCounters();
+ void RecalcAllQuotes();
+
+ // Find the element in the given nsGenConList that directly precedes
+ // the mContent node of this ContainStyleScope in the flat tree. Can
+ // return null if no element in the list precedes the content.
+ nsGenConNode* GetPrecedingElementInGenConList(nsGenConList*);
+
+ private:
+ nsQuoteList mQuoteList;
+ nsCounterManager mCounterManager;
+
+ // We are owned by the |mScopeManager|, so this is guaranteed to be a live
+ // pointer as long as we are alive as well.
+ ContainStyleScopeManager* mScopeManager;
+
+ // Although parent and child relationships are represented as raw pointers
+ // here, |mScopeManager| is responsible for managing creation and deletion of
+ // all these data structures and also that it happens in the correct order.
+ ContainStyleScope* mParent;
+ nsTArray<ContainStyleScope*> mChildren;
+
+ // |mContent| is guaranteed to outlive this scope because mScopeManager will
+ // delete the scope when the corresponding frame for |mContent| is destroyed.
+ nsIContent* mContent;
+};
+
+/* Management of the tree `contain: style` scopes. This class ensures that
+ * recalculation is done top-down, so that nodes that rely on other nodes in
+ * ancestor `contain: style` scopes are calculated properly. */
+class ContainStyleScopeManager {
+ public:
+ ContainStyleScopeManager() : mRootScope(this, nullptr, nullptr) {}
+ ContainStyleScope& GetRootScope() { return mRootScope; }
+ ContainStyleScope& GetOrCreateScopeForContent(nsIContent*);
+ ContainStyleScope& GetScopeForContent(nsIContent*);
+
+ void Clear();
+
+ // If this frame creates a `contain: style` scope, destroy that scope and
+ // all of its child scopes.
+ void DestroyScopesFor(nsIFrame*);
+
+ // Destroy this scope and all its children starting from the leaf nodes.
+ void DestroyScope(ContainStyleScope*);
+
+ bool DestroyCounterNodesFor(nsIFrame*);
+ bool AddCounterChanges(nsIFrame* aNewFrame);
+ nsCounterList* GetOrCreateCounterList(dom::Element&, nsAtom* aCounterName);
+
+ bool CounterDirty(nsAtom* aCounterName);
+ void SetCounterDirty(nsAtom* aCounterName);
+ void RecalcAllCounters();
+ void SetAllCountersDirty();
+
+ bool DestroyQuoteNodesFor(nsIFrame*);
+ nsQuoteList* QuoteListFor(dom::Element&);
+ void RecalcAllQuotes();
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+ void DumpCounters();
+#endif
+
+#ifdef ACCESSIBILITY
+ void GetSpokenCounterText(nsIFrame* aFrame, nsAString& aText);
+#endif
+
+ private:
+ ContainStyleScope mRootScope;
+ nsClassHashtable<nsPtrHashKey<nsIContent>, ContainStyleScope> mScopes;
+ nsTHashSet<RefPtr<nsAtom>> mDirtyCounters;
+};
+
+} // namespace mozilla
+
+#endif /* ContainStyleScopeManager_h_ */
diff --git a/layout/base/DepthOrderedFrameList.cpp b/layout/base/DepthOrderedFrameList.cpp
new file mode 100644
index 0000000000..3c9826d954
--- /dev/null
+++ b/layout/base/DepthOrderedFrameList.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/. */
+
+#include "DepthOrderedFrameList.h"
+#include "nsIFrame.h"
+#include "nsContainerFrame.h"
+
+namespace mozilla {
+
+void DepthOrderedFrameList::Add(nsIFrame* aFrame) {
+ // Is this root already scheduled for reflow?
+ // FIXME: This could possibly be changed to a uniqueness assertion, with some
+ // work in ResizeReflowIgnoreOverride (and maybe others?)
+ // FIXME(emilio): Should probably reuse the traversal for insertion.
+ if (Contains(aFrame)) {
+ // We don't expect frame to change depths.
+ MOZ_ASSERT(aFrame->GetDepthInFrameTree() ==
+ mList[mList.IndexOf(aFrame)].mDepth);
+ return;
+ }
+
+ mList.InsertElementSorted(
+ FrameAndDepth{aFrame, aFrame->GetDepthInFrameTree()},
+ FrameAndDepth::CompareByReverseDepth{});
+}
+
+void DepthOrderedFrameList::Remove(nsIFrame* aFrame) {
+ mList.RemoveElement(aFrame);
+}
+
+nsIFrame* DepthOrderedFrameList::PopShallowestRoot() {
+ // List is sorted in order of decreasing depth, so there are no shallower
+ // frames than the last one.
+ const FrameAndDepth& lastFAD = mList.PopLastElement();
+ nsIFrame* frame = lastFAD.mFrame;
+ // We don't expect frame to change depths.
+ MOZ_ASSERT(frame->GetDepthInFrameTree() == lastFAD.mDepth);
+ return frame;
+}
+
+bool DepthOrderedFrameList::FrameIsAncestorOfAnyElement(
+ nsIFrame* aFrame) const {
+ MOZ_ASSERT(aFrame);
+
+ // Look for a path from any element to aFrame, following GetParent(). This
+ // check mirrors what FrameNeedsReflow() would have done if the reflow root
+ // didn't get in the way.
+ for (nsIFrame* f : mList) {
+ do {
+ if (f == aFrame) {
+ return true;
+ }
+ f = f->GetParent();
+ } while (f);
+ }
+
+ return false;
+}
+
+} // namespace mozilla
diff --git a/layout/base/DepthOrderedFrameList.h b/layout/base/DepthOrderedFrameList.h
new file mode 100644
index 0000000000..a12a5c9c86
--- /dev/null
+++ b/layout/base/DepthOrderedFrameList.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_DepthOrderedFrameList_h
+#define mozilla_DepthOrderedFrameList_h
+
+#include "mozilla/ReverseIterator.h"
+#include "nsTArray.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+class DepthOrderedFrameList {
+ public:
+ // Add a dirty root.
+ void Add(nsIFrame* aFrame);
+ // Remove this frame if present.
+ void Remove(nsIFrame* aFrame);
+ // Remove and return one of the shallowest dirty roots from the list.
+ // (If two roots are at the same depth, order is indeterminate.)
+ nsIFrame* PopShallowestRoot();
+ // Remove all dirty roots.
+ void Clear() { mList.Clear(); }
+ // Is this frame one of the elements in the list?
+ bool Contains(nsIFrame* aFrame) const { return mList.Contains(aFrame); }
+ // Are there no elements?
+ bool IsEmpty() const { return mList.IsEmpty(); }
+
+ // Is the given frame an ancestor of any dirty root?
+ bool FrameIsAncestorOfAnyElement(nsIFrame* aFrame) const;
+
+ auto IterFromShallowest() const { return Reversed(mList); }
+
+ private:
+ struct FrameAndDepth {
+ nsIFrame* mFrame;
+ const uint32_t mDepth;
+
+ // Easy conversion to nsIFrame*, as it's the most likely need.
+ operator nsIFrame*() const { return mFrame; }
+
+ // Used to sort by reverse depths, i.e., deeper < shallower.
+ class CompareByReverseDepth {
+ public:
+ bool Equals(const FrameAndDepth& aA, const FrameAndDepth& aB) const {
+ return aA.mDepth == aB.mDepth;
+ }
+ bool LessThan(const FrameAndDepth& aA, const FrameAndDepth& aB) const {
+ // Reverse depth! So '>' instead of '<'.
+ return aA.mDepth > aB.mDepth;
+ }
+ };
+ };
+ // List of all known dirty roots, sorted by decreasing depths.
+ nsTArray<FrameAndDepth> mList;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/DisplayPortUtils.cpp b/layout/base/DisplayPortUtils.cpp
new file mode 100644
index 0000000000..d9471db153
--- /dev/null
+++ b/layout/base/DisplayPortUtils.cpp
@@ -0,0 +1,977 @@
+/* -*- 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 "DisplayPortUtils.h"
+
+#include "FrameMetrics.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/layers/APZPublicUtils.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/LayersMessageUtils.h"
+#include "mozilla/layers/PAPZ.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPlaceholderFrame.h"
+#include "nsSubDocumentFrame.h"
+#include "RetainedDisplayListBuilder.h"
+#include "WindowRenderer.h"
+
+#include <ostream>
+
+namespace mozilla {
+
+using gfx::IntSize;
+
+using layers::FrameMetrics;
+using layers::ScrollableLayerGuid;
+
+typedef ScrollableLayerGuid::ViewID ViewID;
+
+static LazyLogModule sDisplayportLog("apz.displayport");
+
+/* static */
+DisplayPortMargins DisplayPortMargins::FromAPZ(const ScreenMargin& aMargins,
+ const CSSPoint& aVisualOffset,
+ const CSSPoint& aLayoutOffset) {
+ return DisplayPortMargins{aMargins, aVisualOffset, aLayoutOffset};
+}
+
+/* static */
+DisplayPortMargins DisplayPortMargins::ForScrollFrame(
+ nsIScrollableFrame* aScrollFrame, const ScreenMargin& aMargins) {
+ CSSPoint visualOffset;
+ CSSPoint layoutOffset;
+ if (aScrollFrame) {
+ nsIFrame* scrollFrame = do_QueryFrame(aScrollFrame);
+ PresShell* presShell = scrollFrame->PresShell();
+ layoutOffset = CSSPoint::FromAppUnits(aScrollFrame->GetScrollPosition());
+ if (aScrollFrame->IsRootScrollFrameOfDocument()) {
+ visualOffset =
+ CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset());
+
+ } else {
+ visualOffset = layoutOffset;
+ }
+ }
+ return DisplayPortMargins{aMargins, visualOffset, layoutOffset};
+}
+
+/* static */
+DisplayPortMargins DisplayPortMargins::ForContent(
+ nsIContent* aContent, const ScreenMargin& aMargins) {
+ return ForScrollFrame(
+ aContent ? nsLayoutUtils::FindScrollableFrameFor(aContent) : nullptr,
+ aMargins);
+}
+
+ScreenMargin DisplayPortMargins::GetRelativeToLayoutViewport(
+ ContentGeometryType aGeometryType, nsIScrollableFrame* aScrollableFrame,
+ const CSSToScreenScale2D& aDisplayportScale) const {
+ // APZ wants |mMargins| applied relative to the visual viewport.
+ // The main-thread painting code applies margins relative to
+ // the layout viewport. To get the main thread to paint the
+ // area APZ wants, apply a translation between the two. The
+ // magnitude of the translation depends on whether we are
+ // applying the displayport to scrolled or fixed content.
+ CSSPoint scrollDeltaCss =
+ ComputeAsyncTranslation(aGeometryType, aScrollableFrame);
+ ScreenPoint scrollDelta = scrollDeltaCss * aDisplayportScale;
+ ScreenMargin margins = mMargins;
+ margins.left -= scrollDelta.x;
+ margins.right += scrollDelta.x;
+ margins.top -= scrollDelta.y;
+ margins.bottom += scrollDelta.y;
+ return margins;
+}
+
+std::ostream& operator<<(std::ostream& aOs,
+ const DisplayPortMargins& aMargins) {
+ if (aMargins.mVisualOffset == CSSPoint() &&
+ aMargins.mLayoutOffset == CSSPoint()) {
+ aOs << aMargins.mMargins;
+ } else {
+ aOs << "{" << aMargins.mMargins << "," << aMargins.mVisualOffset << ","
+ << aMargins.mLayoutOffset << "}";
+ }
+ return aOs;
+}
+
+CSSPoint DisplayPortMargins::ComputeAsyncTranslation(
+ ContentGeometryType aGeometryType,
+ nsIScrollableFrame* aScrollableFrame) const {
+ // If we are applying the displayport to scrolled content, the
+ // translation is the entire difference between the visual and
+ // layout offsets.
+ if (aGeometryType == ContentGeometryType::Scrolled) {
+ return mVisualOffset - mLayoutOffset;
+ }
+
+ // If we are applying the displayport to fixed content, only
+ // part of the difference between the visual and layout offsets
+ // should be applied. This is because fixed content remains fixed
+ // to the layout viewport, and some of the async delta between
+ // the visual and layout offsets can drag the layout viewport
+ // with it. We want only the remaining delta, i.e. the offset of
+ // the visual viewport relative to the (async-scrolled) layout
+ // viewport.
+ if (!aScrollableFrame) {
+ // Displayport on a non-scrolling frame for some reason.
+ // There will be no divergence between the two viewports.
+ return CSSPoint();
+ }
+ // Fixed content is always fixed to an RSF.
+ MOZ_ASSERT(aScrollableFrame->IsRootScrollFrameOfDocument());
+ nsIFrame* scrollFrame = do_QueryFrame(aScrollableFrame);
+ if (!scrollFrame->PresShell()->IsVisualViewportSizeSet()) {
+ // Zooming is disabled, so the layout viewport tracks the
+ // visual viewport completely.
+ return CSSPoint();
+ }
+ // Use KeepLayoutViewportEnclosingViewportVisual() to compute
+ // an async layout viewport the way APZ would.
+ const CSSRect visualViewport{
+ mVisualOffset,
+ // TODO: There are probably some edge cases here around async zooming
+ // that are not currently being handled properly. For proper handling,
+ // we'd likely need to save APZ's async zoom when populating
+ // mVisualOffset, and using it to adjust the visual viewport size here.
+ // Note that any incorrectness caused by this will only occur transiently
+ // during async zooming.
+ CSSSize::FromAppUnits(scrollFrame->PresShell()->GetVisualViewportSize())};
+ const CSSRect scrollableRect = CSSRect::FromAppUnits(
+ nsLayoutUtils::CalculateExpandedScrollableRect(scrollFrame));
+ CSSRect asyncLayoutViewport{
+ mLayoutOffset,
+ CSSSize::FromAppUnits(aScrollableFrame->GetScrollPortRect().Size())};
+ FrameMetrics::KeepLayoutViewportEnclosingVisualViewport(
+ visualViewport, scrollableRect, /* out */ asyncLayoutViewport);
+ return mVisualOffset - asyncLayoutViewport.TopLeft();
+}
+
+static nsRect GetDisplayPortFromRectData(nsIContent* aContent,
+ DisplayPortPropertyData* aRectData) {
+ // In the case where the displayport is set as a rect, we assume it is
+ // already aligned and clamped as necessary. The burden to do that is
+ // on the setter of the displayport. In practice very few places set the
+ // displayport directly as a rect (mostly tests).
+ return aRectData->mRect;
+}
+
+static nsRect GetDisplayPortFromMarginsData(
+ nsIContent* aContent, DisplayPortMarginsPropertyData* aMarginsData,
+ const DisplayPortOptions& aOptions) {
+ // In the case where the displayport is set via margins, we apply the margins
+ // to a base rect. Then we align the expanded rect based on the alignment
+ // requested, and finally, clamp it to the size of the scrollable rect.
+
+ nsRect base;
+ if (nsRect* baseData = static_cast<nsRect*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
+ base = *baseData;
+ } else {
+ // In theory we shouldn't get here, but we do sometimes (see bug 1212136).
+ // Fall through for graceful handling.
+ }
+
+ nsIFrame* frame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
+ if (!frame) {
+ // Turns out we can't really compute it. Oops. We still should return
+ // something sane.
+ NS_WARNING(
+ "Attempting to get a displayport from a content with no primary "
+ "frame!");
+ return base;
+ }
+
+ bool isRoot = false;
+ if (aContent->OwnerDoc()->GetRootElement() == aContent) {
+ isRoot = true;
+ }
+
+ nsIScrollableFrame* scrollableFrame = frame->GetScrollTargetFrame();
+ nsPoint scrollPos;
+ if (scrollableFrame) {
+ scrollPos = scrollableFrame->GetScrollPosition();
+ }
+
+ nsPresContext* presContext = frame->PresContext();
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ LayoutDeviceToScreenScale2D res =
+ LayoutDeviceToParentLayerScale(
+ presContext->PresShell()->GetCumulativeResolution()) *
+ nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ frame);
+
+ // Calculate the expanded scrollable rect, which we'll be clamping the
+ // displayport to.
+ nsRect expandedScrollableRect =
+ nsLayoutUtils::CalculateExpandedScrollableRect(frame);
+
+ // GetTransformToAncestorScale() can return 0. In this case, just return the
+ // base rect (clamped to the expanded scrollable rect), as other calculations
+ // would run into divisions by zero.
+ if (res == LayoutDeviceToScreenScale2D(0, 0)) {
+ // Make sure the displayport remains within the scrollable rect.
+ return base.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
+ }
+
+ // First convert the base rect to screen pixels
+ LayoutDeviceToScreenScale2D parentRes = res;
+ if (isRoot) {
+ // the base rect for root scroll frames is specified in the parent document
+ // coordinate space, so it doesn't include the local resolution.
+ float localRes = presContext->PresShell()->GetResolution();
+ parentRes.xScale /= localRes;
+ parentRes.yScale /= localRes;
+ }
+ ScreenRect screenRect =
+ LayoutDeviceRect::FromAppUnits(base, auPerDevPixel) * parentRes;
+
+ // Note on the correctness of applying the alignment in Screen space:
+ // The correct space to apply the alignment in would be Layer space, but
+ // we don't necessarily know the scale to convert to Layer space at this
+ // point because Layout may not yet have chosen the resolution at which to
+ // render (it chooses that in FrameLayerBuilder, but this can be called
+ // during display list building). Therefore, we perform the alignment in
+ // Screen space, which basically assumes that Layout chose to render at
+ // screen resolution; since this is what Layout does most of the time,
+ // this is a good approximation. A proper solution would involve moving
+ // the choosing of the resolution to display-list building time.
+ ScreenSize alignment;
+
+ PresShell* presShell = presContext->PresShell();
+ MOZ_ASSERT(presShell);
+
+ ScreenMargin margins = aMarginsData->mMargins.GetRelativeToLayoutViewport(
+ aOptions.mGeometryType, scrollableFrame,
+ presContext->CSSToDevPixelScale() * res);
+
+ if (presShell->IsDisplayportSuppressed() ||
+ aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
+ alignment = ScreenSize(1, 1);
+ } else {
+ // Moving the displayport is relatively expensive with WR so we use a larger
+ // alignment that causes the displayport to move less frequently. The
+ // alignment scales up with the size of the base rect so larger scrollframes
+ // use a larger alignment, but we clamp the alignment to a power of two
+ // between 128 and 1024 (inclusive).
+ // This naturally also increases the size of the displayport compared to
+ // always using a 128 alignment, so the displayport multipliers are also
+ // correspondingly smaller when WR is enabled to prevent the displayport
+ // from becoming too big.
+ IntSize multiplier =
+ layers::apz::GetDisplayportAlignmentMultiplier(screenRect.Size());
+ alignment = ScreenSize(128 * multiplier.width, 128 * multiplier.height);
+ }
+
+ // Avoid division by zero.
+ if (alignment.width == 0) {
+ alignment.width = 128;
+ }
+ if (alignment.height == 0) {
+ alignment.height = 128;
+ }
+
+ // Expand the rect by the margins
+ screenRect.Inflate(margins);
+
+ ScreenPoint scrollPosScreen =
+ LayoutDevicePoint::FromAppUnits(scrollPos, auPerDevPixel) * res;
+
+ // Align the display port.
+ screenRect += scrollPosScreen;
+ float x = alignment.width * floor(screenRect.x / alignment.width);
+ float y = alignment.height * floor(screenRect.y / alignment.height);
+ float w = alignment.width * ceil(screenRect.width / alignment.width + 1);
+ float h = alignment.height * ceil(screenRect.height / alignment.height + 1);
+ screenRect = ScreenRect(x, y, w, h);
+ screenRect -= scrollPosScreen;
+
+ // Convert the aligned rect back into app units.
+ nsRect result = LayoutDeviceRect::ToAppUnits(screenRect / res, auPerDevPixel);
+
+ // Make sure the displayport remains within the scrollable rect.
+ result = result.MoveInsideAndClamp(expandedScrollableRect - scrollPos);
+
+ return result;
+}
+
+static bool GetDisplayPortData(
+ nsIContent* aContent, DisplayPortPropertyData** aOutRectData,
+ DisplayPortMarginsPropertyData** aOutMarginsData) {
+ MOZ_ASSERT(aOutRectData && aOutMarginsData);
+
+ *aOutRectData = static_cast<DisplayPortPropertyData*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPort));
+ *aOutMarginsData = static_cast<DisplayPortMarginsPropertyData*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+
+ if (!*aOutRectData && !*aOutMarginsData) {
+ // This content element has no displayport data at all
+ return false;
+ }
+
+ if (*aOutRectData && *aOutMarginsData) {
+ // choose margins if equal priority
+ if ((*aOutRectData)->mPriority > (*aOutMarginsData)->mPriority) {
+ *aOutMarginsData = nullptr;
+ } else {
+ *aOutRectData = nullptr;
+ }
+ }
+
+ NS_ASSERTION((*aOutRectData == nullptr) != (*aOutMarginsData == nullptr),
+ "Only one of aOutRectData or aOutMarginsData should be set!");
+
+ return true;
+}
+
+static bool GetWasDisplayPortPainted(nsIContent* aContent) {
+ DisplayPortPropertyData* rectData = nullptr;
+ DisplayPortMarginsPropertyData* marginsData = nullptr;
+
+ if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
+ return false;
+ }
+
+ return rectData ? rectData->mPainted : marginsData->mPainted;
+}
+
+bool DisplayPortUtils::IsMissingDisplayPortBaseRect(nsIContent* aContent) {
+ DisplayPortPropertyData* rectData = nullptr;
+ DisplayPortMarginsPropertyData* marginsData = nullptr;
+
+ if (GetDisplayPortData(aContent, &rectData, &marginsData) && marginsData) {
+ return !aContent->GetProperty(nsGkAtoms::DisplayPortBase);
+ }
+
+ return false;
+}
+
+static void TranslateFromScrollPortToScrollFrame(nsIContent* aContent,
+ nsRect* aRect) {
+ MOZ_ASSERT(aRect);
+ if (nsIScrollableFrame* scrollableFrame =
+ nsLayoutUtils::FindScrollableFrameFor(aContent)) {
+ *aRect += scrollableFrame->GetScrollPortRect().TopLeft();
+ }
+}
+
+static bool GetDisplayPortImpl(nsIContent* aContent, nsRect* aResult,
+ const DisplayPortOptions& aOptions) {
+ DisplayPortPropertyData* rectData = nullptr;
+ DisplayPortMarginsPropertyData* marginsData = nullptr;
+
+ if (!GetDisplayPortData(aContent, &rectData, &marginsData)) {
+ return false;
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame && !frame->PresShell()->AsyncPanZoomEnabled()) {
+ return false;
+ }
+
+ if (!aResult) {
+ // We have displayport data, but the caller doesn't want the actual
+ // rect, so we don't need to actually compute it.
+ return true;
+ }
+
+ bool isDisplayportSuppressed = false;
+
+ if (frame) {
+ nsPresContext* presContext = frame->PresContext();
+ MOZ_ASSERT(presContext);
+ PresShell* presShell = presContext->PresShell();
+ MOZ_ASSERT(presShell);
+ isDisplayportSuppressed = presShell->IsDisplayportSuppressed();
+ }
+
+ nsRect result;
+ if (rectData) {
+ result = GetDisplayPortFromRectData(aContent, rectData);
+ } else if (isDisplayportSuppressed ||
+ nsLayoutUtils::ShouldDisableApzForElement(aContent) ||
+ aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
+ // Note: the above conditions should be in sync with the conditions in
+ // WillUseEmptyDisplayPortMargins.
+
+ // Make a copy of the margins data but set the margins to empty.
+ // Do not create a new DisplayPortMargins object with
+ // DisplayPortMargins::Empty(), because that will record the visual
+ // and layout scroll offsets in place right now on the DisplayPortMargins,
+ // and those are only meant to be recorded when the margins are stored.
+ DisplayPortMarginsPropertyData noMargins = *marginsData;
+ noMargins.mMargins.mMargins = ScreenMargin();
+ result = GetDisplayPortFromMarginsData(aContent, &noMargins, aOptions);
+ } else {
+ result = GetDisplayPortFromMarginsData(aContent, marginsData, aOptions);
+ }
+
+ if (aOptions.mRelativeTo == DisplayportRelativeTo::ScrollFrame) {
+ TranslateFromScrollPortToScrollFrame(aContent, &result);
+ }
+
+ *aResult = result;
+ return true;
+}
+
+bool DisplayPortUtils::GetDisplayPort(nsIContent* aContent, nsRect* aResult,
+ const DisplayPortOptions& aOptions) {
+ return GetDisplayPortImpl(aContent, aResult, aOptions);
+}
+
+bool DisplayPortUtils::HasDisplayPort(nsIContent* aContent) {
+ return GetDisplayPort(aContent, nullptr);
+}
+
+bool DisplayPortUtils::HasPaintedDisplayPort(nsIContent* aContent) {
+ DisplayPortPropertyData* rectData = nullptr;
+ DisplayPortMarginsPropertyData* marginsData = nullptr;
+ GetDisplayPortData(aContent, &rectData, &marginsData);
+ if (rectData) {
+ return rectData->mPainted;
+ }
+ if (marginsData) {
+ return marginsData->mPainted;
+ }
+ return false;
+}
+
+void DisplayPortUtils::MarkDisplayPortAsPainted(nsIContent* aContent) {
+ DisplayPortPropertyData* rectData = nullptr;
+ DisplayPortMarginsPropertyData* marginsData = nullptr;
+ GetDisplayPortData(aContent, &rectData, &marginsData);
+ MOZ_ASSERT(rectData || marginsData,
+ "MarkDisplayPortAsPainted should only be called for an element "
+ "with a displayport");
+ if (rectData) {
+ rectData->mPainted = true;
+ }
+ if (marginsData) {
+ marginsData->mPainted = true;
+ }
+}
+
+bool DisplayPortUtils::HasNonMinimalDisplayPort(nsIContent* aContent) {
+ return HasDisplayPort(aContent) &&
+ !aContent->GetProperty(nsGkAtoms::MinimalDisplayPort);
+}
+
+bool DisplayPortUtils::HasNonMinimalNonZeroDisplayPort(nsIContent* aContent) {
+ if (!HasDisplayPort(aContent)) {
+ return false;
+ }
+ if (aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
+ return false;
+ }
+
+ DisplayPortMarginsPropertyData* currentData =
+ static_cast<DisplayPortMarginsPropertyData*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+
+ if (!currentData) {
+ // We have a display port, so if we don't have margin data we must have rect
+ // data. We consider such as non zero and non minimal, it's probably not too
+ // important as display port rects are only used in tests.
+ return true;
+ }
+
+ if (currentData->mMargins.mMargins != ScreenMargin()) {
+ return true;
+ }
+
+ return false;
+}
+
+/* static */
+bool DisplayPortUtils::GetDisplayPortForVisibilityTesting(nsIContent* aContent,
+ nsRect* aResult) {
+ MOZ_ASSERT(aResult);
+ return GetDisplayPortImpl(
+ aContent, aResult,
+ DisplayPortOptions().With(DisplayportRelativeTo::ScrollFrame));
+}
+
+void DisplayPortUtils::InvalidateForDisplayPortChange(
+ nsIContent* aContent, bool aHadDisplayPort, const nsRect& aOldDisplayPort,
+ const nsRect& aNewDisplayPort, RepaintMode aRepaintMode) {
+ if (aRepaintMode != RepaintMode::Repaint) {
+ return;
+ }
+
+ bool changed =
+ !aHadDisplayPort || !aOldDisplayPort.IsEqualEdges(aNewDisplayPort);
+
+ nsIFrame* frame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
+ if (frame) {
+ frame = do_QueryFrame(frame->GetScrollTargetFrame());
+ }
+
+ if (changed && frame) {
+ // It is important to call SchedulePaint on the same frame that we set the
+ // dirty rect properties on so we can find the frame later to remove the
+ // properties.
+ frame->SchedulePaint();
+
+ if (!nsLayoutUtils::AreRetainedDisplayListsEnabled()) {
+ return;
+ }
+
+ if (StaticPrefs::layout_display_list_retain_sc()) {
+ // DisplayListBuildingDisplayPortRect property is not used when retain sc
+ // mode is enabled.
+ return;
+ }
+
+ auto* builder = nsLayoutUtils::GetRetainedDisplayListBuilder(frame);
+ if (!builder) {
+ return;
+ }
+
+ bool found;
+ nsRect* rect = frame->GetProperty(
+ nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), &found);
+
+ if (!found) {
+ rect = new nsRect();
+ frame->AddProperty(
+ nsDisplayListBuilder::DisplayListBuildingDisplayPortRect(), rect);
+ frame->SetHasOverrideDirtyRegion(true);
+
+ DL_LOGV("Adding display port building rect for frame %p\n", frame);
+ RetainedDisplayListData* data = builder->Data();
+ data->Flags(frame) += RetainedDisplayListData::FrameFlag::HasProps;
+ } else {
+ MOZ_ASSERT(rect, "this property should only store non-null values");
+ }
+
+ if (aHadDisplayPort) {
+ // We only need to build a display list for any new areas added
+ nsRegion newRegion(aNewDisplayPort);
+ newRegion.SubOut(aOldDisplayPort);
+ rect->UnionRect(*rect, newRegion.GetBounds());
+ } else {
+ rect->UnionRect(*rect, aNewDisplayPort);
+ }
+ }
+}
+
+bool DisplayPortUtils::SetDisplayPortMargins(
+ nsIContent* aContent, PresShell* aPresShell,
+ const DisplayPortMargins& aMargins,
+ ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty,
+ uint32_t aPriority, RepaintMode aRepaintMode) {
+ MOZ_ASSERT(aContent);
+ MOZ_ASSERT(aContent->GetComposedDoc() == aPresShell->GetDocument());
+
+ DisplayPortMarginsPropertyData* currentData =
+ static_cast<DisplayPortMarginsPropertyData*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortMargins));
+ if (currentData && currentData->mPriority > aPriority) {
+ return false;
+ }
+
+ if (currentData && currentData->mMargins.mVisualOffset != CSSPoint() &&
+ aMargins.mVisualOffset == CSSPoint()) {
+ // If we hit this, then it's possible that we're setting a displayport
+ // that is wrong because the old one had a layout/visual adjustment and
+ // the new one does not.
+ MOZ_LOG(sDisplayportLog, LogLevel::Warning,
+ ("Dropping visual offset %s",
+ ToString(currentData->mMargins.mVisualOffset).c_str()));
+ }
+
+ nsIFrame* scrollFrame = nsLayoutUtils::GetScrollFrameFromContent(aContent);
+
+ nsRect oldDisplayPort;
+ bool hadDisplayPort = false;
+ bool wasPainted = GetWasDisplayPortPainted(aContent);
+ if (scrollFrame) {
+ // We only use the two return values from this function to call
+ // InvalidateForDisplayPortChange. InvalidateForDisplayPortChange does
+ // nothing if aContent does not have a frame. So getting the displayport is
+ // useless if the content has no frame, so we avoid calling this to avoid
+ // triggering a warning about not having a frame.
+ hadDisplayPort = GetDisplayPort(aContent, &oldDisplayPort);
+ }
+
+ aContent->SetProperty(
+ nsGkAtoms::DisplayPortMargins,
+ new DisplayPortMarginsPropertyData(aMargins, aPriority, wasPainted),
+ nsINode::DeleteProperty<DisplayPortMarginsPropertyData>);
+
+ if (aClearMinimalDisplayPortProperty ==
+ ClearMinimalDisplayPortProperty::Yes) {
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug) &&
+ aContent->GetProperty(nsGkAtoms::MinimalDisplayPort)) {
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsLayoutUtils::FindIDFor(aContent, &viewID);
+ MOZ_LOG(sDisplayportLog, LogLevel::Debug,
+ ("SetDisplayPortMargins removing MinimalDisplayPort prop on "
+ "scrollId=%" PRIu64 "\n",
+ viewID));
+ }
+ aContent->RemoveProperty(nsGkAtoms::MinimalDisplayPort);
+ }
+
+ nsIScrollableFrame* scrollableFrame =
+ scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
+ if (!scrollableFrame) {
+ return true;
+ }
+
+ nsRect newDisplayPort;
+ DebugOnly<bool> hasDisplayPort = GetDisplayPort(aContent, &newDisplayPort);
+ MOZ_ASSERT(hasDisplayPort);
+
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Debug)) {
+ mozilla::layers::ScrollableLayerGuid::ViewID viewID =
+ mozilla::layers::ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsLayoutUtils::FindIDFor(aContent, &viewID);
+ if (!hadDisplayPort) {
+ MOZ_LOG(sDisplayportLog, LogLevel::Debug,
+ ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
+ ToString(aMargins).c_str(), viewID,
+ ToString(newDisplayPort).c_str()));
+ } else {
+ // Use verbose level logging for when an existing displayport got its
+ // margins updated.
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("SetDisplayPortMargins %s on scrollId=%" PRIu64 ", newDp=%s\n",
+ ToString(aMargins).c_str(), viewID,
+ ToString(newDisplayPort).c_str()));
+ }
+ }
+
+ InvalidateForDisplayPortChange(aContent, hadDisplayPort, oldDisplayPort,
+ newDisplayPort, aRepaintMode);
+
+ scrollableFrame->TriggerDisplayPortExpiration();
+
+ // Display port margins changing means that the set of visible frames may
+ // have drastically changed. Check if we should schedule an update.
+ hadDisplayPort =
+ scrollableFrame->GetDisplayPortAtLastApproximateFrameVisibilityUpdate(
+ &oldDisplayPort);
+
+ bool needVisibilityUpdate = !hadDisplayPort;
+ // Check if the total size has changed by a large factor.
+ if (!needVisibilityUpdate) {
+ if ((newDisplayPort.width > 2 * oldDisplayPort.width) ||
+ (oldDisplayPort.width > 2 * newDisplayPort.width) ||
+ (newDisplayPort.height > 2 * oldDisplayPort.height) ||
+ (oldDisplayPort.height > 2 * newDisplayPort.height)) {
+ needVisibilityUpdate = true;
+ }
+ }
+ // Check if it's moved by a significant amount.
+ if (!needVisibilityUpdate) {
+ if (nsRect* baseData = static_cast<nsRect*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortBase))) {
+ nsRect base = *baseData;
+ if ((std::abs(newDisplayPort.X() - oldDisplayPort.X()) > base.width) ||
+ (std::abs(newDisplayPort.XMost() - oldDisplayPort.XMost()) >
+ base.width) ||
+ (std::abs(newDisplayPort.Y() - oldDisplayPort.Y()) > base.height) ||
+ (std::abs(newDisplayPort.YMost() - oldDisplayPort.YMost()) >
+ base.height)) {
+ needVisibilityUpdate = true;
+ }
+ }
+ }
+ if (needVisibilityUpdate) {
+ aPresShell->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+
+ return true;
+}
+
+void DisplayPortUtils::SetDisplayPortBase(nsIContent* aContent,
+ const nsRect& aBase) {
+ if (MOZ_LOG_TEST(sDisplayportLog, LogLevel::Verbose)) {
+ ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(aContent);
+ MOZ_LOG(sDisplayportLog, LogLevel::Verbose,
+ ("Setting base rect %s for scrollId=%" PRIu64 "\n",
+ ToString(aBase).c_str(), viewId));
+ }
+ aContent->SetProperty(nsGkAtoms::DisplayPortBase, new nsRect(aBase),
+ nsINode::DeleteProperty<nsRect>);
+}
+
+void DisplayPortUtils::SetDisplayPortBaseIfNotSet(nsIContent* aContent,
+ const nsRect& aBase) {
+ if (!aContent->GetProperty(nsGkAtoms::DisplayPortBase)) {
+ SetDisplayPortBase(aContent, aBase);
+ }
+}
+
+void DisplayPortUtils::RemoveDisplayPort(nsIContent* aContent) {
+ aContent->RemoveProperty(nsGkAtoms::DisplayPort);
+ aContent->RemoveProperty(nsGkAtoms::DisplayPortMargins);
+}
+
+bool DisplayPortUtils::ViewportHasDisplayPort(nsPresContext* aPresContext) {
+ nsIFrame* rootScrollFrame = aPresContext->PresShell()->GetRootScrollFrame();
+ return rootScrollFrame && HasDisplayPort(rootScrollFrame->GetContent());
+}
+
+bool DisplayPortUtils::IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame) {
+ // Fixed-pos frames are parented by the viewport frame or the page content
+ // frame. We'll assume that printing/print preview don't have displayports for
+ // their pages!
+ nsIFrame* parent = aFrame->GetParent();
+ if (!parent || parent->GetParent() ||
+ aFrame->StyleDisplay()->mPosition != StylePositionProperty::Fixed) {
+ return false;
+ }
+ return ViewportHasDisplayPort(aFrame->PresContext());
+}
+
+// We want to this return true for the scroll frame, but not the
+// scrolled frame (which has the same content).
+bool DisplayPortUtils::FrameHasDisplayPort(nsIFrame* aFrame,
+ const nsIFrame* aScrolledFrame) {
+ if (!aFrame->GetContent() || !HasDisplayPort(aFrame->GetContent())) {
+ return false;
+ }
+ nsIScrollableFrame* sf = do_QueryFrame(aFrame);
+ if (sf) {
+ if (aScrolledFrame && aScrolledFrame != sf->GetScrolledFrame()) {
+ return false;
+ }
+ return true;
+ }
+ return false;
+}
+
+bool DisplayPortUtils::CalculateAndSetDisplayPortMargins(
+ nsIScrollableFrame* aScrollFrame, RepaintMode aRepaintMode) {
+ nsIFrame* frame = do_QueryFrame(aScrollFrame);
+ MOZ_ASSERT(frame);
+ nsIContent* content = frame->GetContent();
+ MOZ_ASSERT(content);
+
+ FrameMetrics metrics =
+ nsLayoutUtils::CalculateBasicFrameMetrics(aScrollFrame);
+ ScreenMargin displayportMargins = layers::apz::CalculatePendingDisplayPort(
+ metrics, ParentLayerPoint(0.0f, 0.0f));
+ PresShell* presShell = frame->PresContext()->GetPresShell();
+
+ DisplayPortMargins margins =
+ DisplayPortMargins::ForScrollFrame(aScrollFrame, displayportMargins);
+
+ return SetDisplayPortMargins(content, presShell, margins,
+ ClearMinimalDisplayPortProperty::Yes, 0,
+ aRepaintMode);
+}
+
+bool DisplayPortUtils::MaybeCreateDisplayPort(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aScrollFrame,
+ nsIScrollableFrame* aScrollFrameAsScrollable, RepaintMode aRepaintMode) {
+ MOZ_ASSERT(aBuilder->IsPaintingToWindow());
+
+ nsIContent* content = aScrollFrame->GetContent();
+ if (!content) {
+ return false;
+ }
+
+ // We perform an optimization where we ensure that at least one
+ // async-scrollable frame (i.e. one that WantsAsyncScroll()) has a
+ // displayport. If that's not the case yet, and we are async-scrollable, we
+ // will get a displayport.
+ MOZ_ASSERT(nsLayoutUtils::AsyncPanZoomEnabled(aScrollFrame));
+ if (!aBuilder->HaveScrollableDisplayPort() &&
+ aScrollFrameAsScrollable->WantAsyncScroll()) {
+ bool haveDisplayPort = HasNonMinimalNonZeroDisplayPort(content);
+ // If we don't already have a displayport, calculate and set one.
+ if (!haveDisplayPort) {
+ // We only use the viewId for logging purposes, but create it
+ // unconditionally to minimize impact of enabling logging. If we don't
+ // assign a viewId here it will get assigned later anyway so functionally
+ // there should be no difference.
+ ViewID viewId = nsLayoutUtils::FindOrCreateIDFor(content);
+ MOZ_LOG(
+ sDisplayportLog, LogLevel::Debug,
+ ("Setting DP on first-encountered scrollId=%" PRIu64 "\n", viewId));
+
+ CalculateAndSetDisplayPortMargins(aScrollFrameAsScrollable, aRepaintMode);
+#ifdef DEBUG
+ haveDisplayPort = HasNonMinimalDisplayPort(content);
+ MOZ_ASSERT(haveDisplayPort,
+ "should have a displayport after having just set it");
+#endif
+ }
+
+ // Record that the we now have a scrollable display port.
+ aBuilder->SetHaveScrollableDisplayPort();
+ return true;
+ }
+ return false;
+}
+void DisplayPortUtils::SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ nsIFrame* aFrame) {
+ nsIFrame* frame = aFrame;
+ while (frame) {
+ frame = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(frame);
+ if (!frame) {
+ break;
+ }
+ nsIScrollableFrame* scrollAncestor =
+ nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
+ if (!scrollAncestor) {
+ break;
+ }
+ frame = do_QueryFrame(scrollAncestor);
+ MOZ_ASSERT(frame);
+ MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
+ frame->PresShell()->GetRootScrollFrame() == frame);
+ if (nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
+ !HasDisplayPort(frame->GetContent())) {
+ SetDisplayPortMargins(frame->GetContent(), frame->PresShell(),
+ DisplayPortMargins::Empty(frame->GetContent()),
+ ClearMinimalDisplayPortProperty::No, 0,
+ RepaintMode::Repaint);
+ }
+ }
+}
+
+bool DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
+ nsIFrame* aFrame, nsDisplayListBuilder* aBuilder) {
+ // Don't descend into the tab bar in chrome, it can be very large and does not
+ // contain any async scrollable elements.
+ if (XRE_IsParentProcess() && aFrame->GetContent() &&
+ aFrame->GetContent()->GetID() == nsGkAtoms::tabbrowser_arrowscrollbox) {
+ return false;
+ }
+ if (aFrame->IsScrollContainer()) {
+ if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
+ if (MaybeCreateDisplayPort(aBuilder, aFrame, sf, RepaintMode::Repaint)) {
+ // If this was the first displayport found in the first scroll frame
+ // encountered, mark the scroll frame with the current paint sequence
+ // number. This is used later to ensure the displayport created is
+ // never expired. When there is a scrollable frame with a first
+ // scrollable sequence number found that does not match the current
+ // paint sequence number (may occur if the dom was mutated in some way),
+ // the value will be reset.
+ sf->SetIsFirstScrollableFrameSequenceNumber(
+ Some(nsDisplayListBuilder::GetPaintSequenceNumber()));
+ return true;
+ }
+ }
+ } else if (aFrame->IsPlaceholderFrame()) {
+ nsPlaceholderFrame* placeholder = static_cast<nsPlaceholderFrame*>(aFrame);
+ nsIFrame* oof = placeholder->GetOutOfFlowFrame();
+ if (oof && !nsLayoutUtils::IsPopup(oof) &&
+ MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof, aBuilder)) {
+ return true;
+ }
+ } else if (aFrame->IsSubDocumentFrame()) {
+ PresShell* presShell = static_cast<nsSubDocumentFrame*>(aFrame)
+ ->GetSubdocumentPresShellForPainting(0);
+ if (nsIFrame* root = presShell ? presShell->GetRootFrame() : nullptr) {
+ if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(root, aBuilder)) {
+ return true;
+ }
+ }
+ }
+ if (aFrame->StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
+ // Only descend the visible card of deck / tabpanels
+ return false;
+ }
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ if (MaybeCreateDisplayPortInFirstScrollFrameEncountered(child, aBuilder)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void DisplayPortUtils::ExpireDisplayPortOnAsyncScrollableAncestor(
+ nsIFrame* aFrame) {
+ nsIFrame* frame = aFrame;
+ while (frame) {
+ frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
+ if (!frame) {
+ break;
+ }
+ nsIScrollableFrame* scrollAncestor =
+ nsLayoutUtils::GetAsyncScrollableAncestorFrame(frame);
+ if (!scrollAncestor) {
+ break;
+ }
+ frame = do_QueryFrame(scrollAncestor);
+ MOZ_ASSERT(frame);
+ if (!frame) {
+ break;
+ }
+ MOZ_ASSERT(scrollAncestor->WantAsyncScroll() ||
+ frame->PresShell()->GetRootScrollFrame() == frame);
+ if (HasDisplayPort(frame->GetContent())) {
+ scrollAncestor->TriggerDisplayPortExpiration();
+ // Stop after the first trigger. If it failed, there's no point in
+ // continuing because all the rest of the frames we encounter are going
+ // to be ancestors of |scrollAncestor| which will keep its displayport.
+ // If the trigger succeeded, we stop because when the trigger executes
+ // it will call this function again to trigger the next ancestor up the
+ // chain.
+ break;
+ }
+ }
+}
+
+Maybe<nsRect> DisplayPortUtils::GetRootDisplayportBase(PresShell* aPresShell) {
+ DebugOnly<nsPresContext*> pc = aPresShell->GetPresContext();
+ MOZ_ASSERT(pc, "this function should be called after PresShell::Init");
+ MOZ_ASSERT(pc->IsRootContentDocumentCrossProcess() ||
+ !pc->GetParentPresContext());
+
+ dom::BrowserChild* browserChild = dom::BrowserChild::GetFrom(aPresShell);
+ if (browserChild && !browserChild->IsTopLevel()) {
+ // If this is an in-process root in on OOP iframe, use the visible rect if
+ // it's been set.
+ return browserChild->GetVisibleRect();
+ }
+
+ nsIFrame* frame = aPresShell->GetRootScrollFrame();
+ if (!frame) {
+ frame = aPresShell->GetRootFrame();
+ }
+
+ nsRect baseRect;
+ if (frame) {
+ baseRect = nsRect(nsPoint(0, 0),
+ nsLayoutUtils::CalculateCompositionSizeForFrame(frame));
+ } else {
+ baseRect = nsRect(nsPoint(0, 0),
+ aPresShell->GetPresContext()->GetVisibleArea().Size());
+ }
+
+ return Some(baseRect);
+}
+
+bool DisplayPortUtils::WillUseEmptyDisplayPortMargins(nsIContent* aContent) {
+ MOZ_ASSERT(HasDisplayPort(aContent));
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return false;
+ }
+
+ // Note these conditions should be in sync with the conditions where we use
+ // empty margins to calculate display port in GetDisplayPortImpl
+ return aContent->GetProperty(nsGkAtoms::MinimalDisplayPort) ||
+ frame->PresShell()->IsDisplayportSuppressed() ||
+ nsLayoutUtils::ShouldDisableApzForElement(aContent);
+}
+
+} // namespace mozilla
diff --git a/layout/base/DisplayPortUtils.h b/layout/base/DisplayPortUtils.h
new file mode 100644
index 0000000000..7c9b8b8610
--- /dev/null
+++ b/layout/base/DisplayPortUtils.h
@@ -0,0 +1,312 @@
+/* -*- 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_DisplayPortUtils_h__
+#define mozilla_DisplayPortUtils_h__
+
+#include "Units.h"
+#include "nsDisplayList.h"
+#include "nsRect.h"
+
+#include <cstdint>
+#include <iosfwd>
+
+class nsIContent;
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+
+class nsDisplayListBuilder;
+class PresShell;
+
+// For GetDisplayPort
+enum class DisplayportRelativeTo { ScrollPort, ScrollFrame };
+
+// Is the displayport being applied to scrolled content or fixed content?
+enum class ContentGeometryType { Scrolled, Fixed };
+
+struct DisplayPortOptions {
+ // The default options.
+ DisplayportRelativeTo mRelativeTo = DisplayportRelativeTo::ScrollPort;
+ ContentGeometryType mGeometryType = ContentGeometryType::Scrolled;
+
+ // Fluent interface for changing the defaults.
+ DisplayPortOptions With(DisplayportRelativeTo aRelativeTo) const {
+ DisplayPortOptions result = *this;
+ result.mRelativeTo = aRelativeTo;
+ return result;
+ }
+ DisplayPortOptions With(ContentGeometryType aGeometryType) const {
+ DisplayPortOptions result = *this;
+ result.mGeometryType = aGeometryType;
+ return result;
+ }
+};
+
+struct DisplayPortPropertyData {
+ DisplayPortPropertyData(const nsRect& aRect, uint32_t aPriority,
+ bool aPainted)
+ : mRect(aRect), mPriority(aPriority), mPainted(aPainted) {}
+ nsRect mRect;
+ uint32_t mPriority;
+ bool mPainted;
+};
+
+struct DisplayPortMargins {
+ // The margins relative to the visual scroll offset.
+ ScreenMargin mMargins;
+
+ // Some information captured at the time the margins are stored.
+ // This ensures that we can express the margins as being relative to
+ // the correct scroll offset when applying them.
+
+ // APZ's visual scroll offset at the time it requested the margins.
+ CSSPoint mVisualOffset;
+
+ // The scroll frame's layout scroll offset at the time the margins
+ // were saved.
+ CSSPoint mLayoutOffset;
+
+ // Create displayport margins requested by APZ, relative to an async visual
+ // offset provided by APZ.
+ static DisplayPortMargins FromAPZ(const ScreenMargin& aMargins,
+ const CSSPoint& aVisualOffset,
+ const CSSPoint& aLayoutOffset);
+
+ // Create displayport port margins for the given scroll frame.
+ // This is for use in cases where we don't have async scroll information from
+ // APZ to use to adjust the margins. The visual and layout offset are set
+ // based on the main thread's view of them.
+ static DisplayPortMargins ForScrollFrame(nsIScrollableFrame* aScrollFrame,
+ const ScreenMargin& aMargins);
+
+ // Convenience version of the above that takes a content element.
+ static DisplayPortMargins ForContent(nsIContent* aContent,
+ const ScreenMargin& aMargins);
+
+ // Another convenience version that sets empty margins.
+ static DisplayPortMargins Empty(nsIContent* aContent) {
+ return ForContent(aContent, ScreenMargin());
+ }
+
+ // Get the margins relative to the layout viewport.
+ // |aGeometryType| tells us whether the margins are being queried for the
+ // purpose of being applied to scrolled content or fixed content.
+ // |aScrollableFrame| is the scroll frame whose content the margins will be
+ // applied to (or, in the case of fixed content), the scroll frame wrt. which
+ // the content is fixed.
+ ScreenMargin GetRelativeToLayoutViewport(
+ ContentGeometryType aGeometryType, nsIScrollableFrame* aScrollableFrame,
+ const CSSToScreenScale2D& aDisplayportScale) const;
+
+ friend std::ostream& operator<<(std::ostream& aOs,
+ const DisplayPortMargins& aMargins);
+
+ private:
+ CSSPoint ComputeAsyncTranslation(ContentGeometryType aGeometryType,
+ nsIScrollableFrame* aScrollableFrame) const;
+};
+
+struct DisplayPortMarginsPropertyData {
+ DisplayPortMarginsPropertyData(const DisplayPortMargins& aMargins,
+ uint32_t aPriority, bool aPainted)
+ : mMargins(aMargins), mPriority(aPriority), mPainted(aPainted) {}
+ DisplayPortMargins mMargins;
+ uint32_t mPriority;
+ bool mPainted;
+};
+
+class DisplayPortUtils {
+ public:
+ /**
+ * Get display port for the given element, relative to the specified entity,
+ * defaulting to the scrollport.
+ */
+ static bool GetDisplayPort(
+ nsIContent* aContent, nsRect* aResult,
+ const DisplayPortOptions& aOptions = DisplayPortOptions());
+
+ /**
+ * Check whether the given element has a displayport.
+ */
+ static bool HasDisplayPort(nsIContent* aContent);
+
+ /**
+ * Check whether the given element has a displayport that has already
+ * been sent to the compositor via a layers or WR transaction.
+ */
+ static bool HasPaintedDisplayPort(nsIContent* aContent);
+
+ /**
+ * Mark the displayport of a given element as having been sent to
+ * the compositor via a layers or WR transaction.
+ */
+ static void MarkDisplayPortAsPainted(nsIContent* aContent);
+
+ /**
+ * Check whether the given frame has a displayport. It returns false
+ * for scrolled frames and true for the corresponding scroll frame.
+ * Optionally pass the child, and it only returns true if the child is the
+ * scrolled frame for the displayport.
+ */
+ static bool FrameHasDisplayPort(nsIFrame* aFrame,
+ const nsIFrame* aScrolledFrame = nullptr);
+
+ /**
+ * Check whether the given element has a non-minimal displayport.
+ */
+ static bool HasNonMinimalDisplayPort(nsIContent* aContent);
+
+ /**
+ * Check whether the given element has a non-minimal displayport that also has
+ * non-zero margins. A display port rect is considered non-minimal non-zero.
+ */
+ static bool HasNonMinimalNonZeroDisplayPort(nsIContent* aContent);
+
+ /**
+ * Check if the given element has a margins based displayport but is missing a
+ * displayport base rect that it needs to properly compute a displayport rect.
+ */
+ static bool IsMissingDisplayPortBaseRect(nsIContent* aContent);
+
+ /**
+ * @return the display port for the given element which should be used for
+ * visibility testing purposes, relative to the scroll frame.
+ *
+ * This is the display port computed with a multipler of 1 which is the normal
+ * display port unless low-precision buffers are enabled. If low-precision
+ * buffers are enabled then GetDisplayPort() uses a multiplier to expand the
+ * displayport, so this will differ from GetDisplayPort.
+ */
+ static bool GetDisplayPortForVisibilityTesting(nsIContent* aContent,
+ nsRect* aResult);
+
+ enum class RepaintMode : uint8_t { Repaint, DoNotRepaint };
+
+ /**
+ * Invalidate for displayport change.
+ */
+ static void InvalidateForDisplayPortChange(
+ nsIContent* aContent, bool aHadDisplayPort, const nsRect& aOldDisplayPort,
+ const nsRect& aNewDisplayPort,
+ RepaintMode aRepaintMode = RepaintMode::Repaint);
+
+ /**
+ * Set the display port margins for a content element to be used with a
+ * display port base (see SetDisplayPortBase()).
+ * See also nsIDOMWindowUtils.setDisplayPortMargins.
+ * @param aContent the content element for which to set the margins
+ * @param aPresShell the pres shell for the document containing the element
+ * @param aMargins the margins to set
+ * @param aAlignmentX, alignmentY the amount of pixels to which to align the
+ * displayport built by combining the base
+ * rect with the margins, in either direction
+ * @param aPriority a priority value to determine which margins take effect
+ * when multiple callers specify margins
+ * @param aRepaintMode whether to schedule a paint after setting the margins
+ * @return true if the new margins were applied.
+ */
+ enum class ClearMinimalDisplayPortProperty { No, Yes };
+
+ static bool SetDisplayPortMargins(
+ nsIContent* aContent, PresShell* aPresShell,
+ const DisplayPortMargins& aMargins,
+ ClearMinimalDisplayPortProperty aClearMinimalDisplayPortProperty,
+ uint32_t aPriority = 0, RepaintMode aRepaintMode = RepaintMode::Repaint);
+
+ /**
+ * Set the display port base rect for given element to be used with display
+ * port margins.
+ * SetDisplayPortBaseIfNotSet is like SetDisplayPortBase except it only sets
+ * the display port base to aBase if no display port base is currently set.
+ */
+ static void SetDisplayPortBase(nsIContent* aContent, const nsRect& aBase);
+ static void SetDisplayPortBaseIfNotSet(nsIContent* aContent,
+ const nsRect& aBase);
+
+ /**
+ * Remove the displayport for the given element.
+ */
+ static void RemoveDisplayPort(nsIContent* aContent);
+
+ /**
+ * Return true if aPresContext's viewport has a displayport.
+ */
+ static bool ViewportHasDisplayPort(nsPresContext* aPresContext);
+
+ /**
+ * Return true if aFrame is a fixed-pos frame and is a child of a viewport
+ * which has a displayport. These frames get special treatment from the
+ * compositor. aDisplayPort, if non-null, is set to the display port rectangle
+ * (relative to the viewport).
+ */
+ static bool IsFixedPosFrameInDisplayPort(const nsIFrame* aFrame);
+
+ static bool MaybeCreateDisplayPortInFirstScrollFrameEncountered(
+ nsIFrame* aFrame, nsDisplayListBuilder* aBuilder);
+
+ /**
+ * Calculate a default set of displayport margins for the given scrollframe
+ * and set them on the scrollframe's content element. The margins are set with
+ * the default priority, which may clobber previously set margins. The repaint
+ * mode provided is passed through to the call to SetDisplayPortMargins.
+ * The |aScrollFrame| parameter must be non-null and queryable to an nsIFrame.
+ * @return true iff the call to SetDisplayPortMargins returned true.
+ */
+ static bool CalculateAndSetDisplayPortMargins(
+ nsIScrollableFrame* aScrollFrame, RepaintMode aRepaintMode);
+
+ /**
+ * If |aScrollFrame| WantsAsyncScroll() and we don't have a scrollable
+ * displayport yet (as tracked by |aBuilder|), calculate and set a
+ * displayport.
+ *
+ * If this is called during display list building pass DoNotRepaint in
+ * aRepaintMode.
+ *
+ * Returns true if there is a displayport on an async scrollable scrollframe
+ * after this call, either because one was just added or it already existed.
+ */
+ static bool MaybeCreateDisplayPort(
+ nsDisplayListBuilder* aBuilder, nsIFrame* aScrollFrame,
+ nsIScrollableFrame* aScrollFrameAsScrollable, RepaintMode aRepaintMode);
+
+ /**
+ * Sets a zero margin display port on all proper ancestors of aFrame that
+ * are async scrollable.
+ */
+ static void SetZeroMarginDisplayPortOnAsyncScrollableAncestors(
+ nsIFrame* aFrame);
+
+ /**
+ * Finds the closest ancestor async scrollable frame from aFrame that has a
+ * displayport and attempts to trigger the displayport expiry on that
+ * ancestor.
+ */
+ static void ExpireDisplayPortOnAsyncScrollableAncestor(nsIFrame* aFrame);
+
+ /**
+ * Returns root displayport base rect for |aPresShell|. In the case where
+ * |aPresShell| is in an out-of-process iframe, this function may return
+ * Nothing() if we haven't received the iframe's visible rect from the parent
+ * content.
+ * |aPresShell| should be top level content or in-process root or root in the
+ * browser process.
+ */
+ static Maybe<nsRect> GetRootDisplayportBase(PresShell* aPresShell);
+
+ /**
+ * Whether to tell the given element will use empty displayport marings.
+ * NOTE: This function should be called only for the element having any type
+ * of displayports.
+ */
+ static bool WillUseEmptyDisplayPortMargins(nsIContent* aContent);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_DisplayPortUtils_h__
diff --git a/layout/base/FrameProperties.h b/layout/base/FrameProperties.h
new file mode 100644
index 0000000000..1a8021c387
--- /dev/null
+++ b/layout/base/FrameProperties.h
@@ -0,0 +1,435 @@
+/* -*- 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 FRAMEPROPERTIES_H_
+#define FRAMEPROPERTIES_H_
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/Unused.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+class nsIFrame;
+
+namespace mozilla {
+
+struct FramePropertyDescriptorUntyped {
+ /**
+ * mDestructor will be called if it's non-null.
+ */
+ typedef void UntypedDestructor(void* aPropertyValue);
+ UntypedDestructor* mDestructor;
+ /**
+ * mDestructorWithFrame will be called if it's non-null and mDestructor
+ * is null. WARNING: The frame passed to mDestructorWithFrame may
+ * be a dangling frame pointer, if this is being called during
+ * presshell teardown. Do not use it except to compare against
+ * other frame pointers. No frame will have been allocated with
+ * the same address yet.
+ */
+ typedef void UntypedDestructorWithFrame(const nsIFrame* aFrame,
+ void* aPropertyValue);
+ UntypedDestructorWithFrame* mDestructorWithFrame;
+ /**
+ * mDestructor and mDestructorWithFrame may both be null, in which case
+ * no value destruction is a no-op.
+ */
+
+ protected:
+ /**
+ * At most one destructor should be passed in. In general, you should
+ * just use the static function FramePropertyDescriptor::New* below
+ * instead of using this constructor directly.
+ */
+ constexpr FramePropertyDescriptorUntyped(
+ UntypedDestructor* aDtor, UntypedDestructorWithFrame* aDtorWithFrame)
+ : mDestructor(aDtor), mDestructorWithFrame(aDtorWithFrame) {}
+};
+
+/**
+ * A pointer to a FramePropertyDescriptor serves as a unique property ID.
+ * The FramePropertyDescriptor stores metadata about the property.
+ * Currently the only metadata is a destructor function. The destructor
+ * function is called on property values when they are overwritten or
+ * deleted.
+ *
+ * To use this class, declare a global (i.e., file, class or function-scope
+ * static member) FramePropertyDescriptor and pass its address as
+ * aProperty in the FrameProperties methods.
+ */
+template <typename T>
+struct FramePropertyDescriptor : public FramePropertyDescriptorUntyped {
+ typedef void Destructor(T* aPropertyValue);
+ typedef void DestructorWithFrame(const nsIFrame* aFrame, T* aPropertyValue);
+
+ template <Destructor Dtor>
+ static constexpr const FramePropertyDescriptor<T> NewWithDestructor() {
+ return {Destruct<Dtor>, nullptr};
+ }
+
+ template <DestructorWithFrame Dtor>
+ static constexpr const FramePropertyDescriptor<T>
+ NewWithDestructorWithFrame() {
+ return {nullptr, DestructWithFrame<Dtor>};
+ }
+
+ static constexpr const FramePropertyDescriptor<T> NewWithoutDestructor() {
+ return {nullptr, nullptr};
+ }
+
+ private:
+ constexpr FramePropertyDescriptor(UntypedDestructor* aDtor,
+ UntypedDestructorWithFrame* aDtorWithFrame)
+ : FramePropertyDescriptorUntyped(aDtor, aDtorWithFrame) {}
+
+ template <Destructor Dtor>
+ static void Destruct(void* aPropertyValue) {
+ Dtor(static_cast<T*>(aPropertyValue));
+ }
+
+ template <DestructorWithFrame Dtor>
+ static void DestructWithFrame(const nsIFrame* aFrame, void* aPropertyValue) {
+ Dtor(aFrame, static_cast<T*>(aPropertyValue));
+ }
+};
+
+// SmallValueHolder<T> is a placeholder intended to be used as template
+// argument of FramePropertyDescriptor for types which can fit directly into our
+// internal value slot (i.e. types that can fit in 64 bits). This class should
+// never be defined, so that we won't use it for unexpected purpose by mistake.
+template <typename T>
+class SmallValueHolder;
+
+namespace detail {
+
+template <typename T>
+struct FramePropertyTypeHelper {
+ typedef T* Type;
+};
+template <typename T>
+struct FramePropertyTypeHelper<SmallValueHolder<T>> {
+ typedef T Type;
+};
+
+} // namespace detail
+
+/**
+ * The FrameProperties class is optimized for storing 0 or 1 properties on
+ * a given frame. Storing very large numbers of properties on a single
+ * frame will not be efficient.
+ */
+class FrameProperties {
+ public:
+ template <typename T>
+ using Descriptor = const FramePropertyDescriptor<T>*;
+ using UntypedDescriptor = const FramePropertyDescriptorUntyped*;
+
+ template <typename T>
+ using PropertyType = typename detail::FramePropertyTypeHelper<T>::Type;
+
+ explicit FrameProperties() = default;
+
+ ~FrameProperties() {
+ MOZ_ASSERT(mProperties.Length() == 0, "forgot to delete properties");
+ }
+
+ /**
+ * Return true if we have no properties, otherwise return false.
+ */
+ bool IsEmpty() const { return mProperties.IsEmpty(); }
+
+ /**
+ * Set a property value. This requires a linear search through
+ * the properties of the frame. Any existing value for the property
+ * is destroyed.
+ */
+ template <typename T>
+ void Set(Descriptor<T> aProperty, PropertyType<T> aValue,
+ const nsIFrame* aFrame) {
+ uint64_t v = ReinterpretHelper<T>::ToInternalValue(aValue);
+ SetInternal(aProperty, v, aFrame);
+ }
+
+ /**
+ * Add a property value; the descriptor MUST NOT already be present.
+ */
+ template <typename T>
+ void Add(Descriptor<T> aProperty, PropertyType<T> aValue) {
+ MOZ_ASSERT(!Has(aProperty), "duplicate frame property");
+ uint64_t v = ReinterpretHelper<T>::ToInternalValue(aValue);
+ AddInternal(aProperty, v);
+ }
+
+ /**
+ * @return true if @aProperty is set. This requires a linear search through
+ * the properties of the frame.
+ *
+ * In most cases, this shouldn't be used outside of assertions, because if
+ * you're doing a lookup anyway it would be far more efficient to call Get()
+ * or Take() and check the aFoundResult outparam to find out whether the
+ * property is set. Legitimate non-assertion uses include:
+ *
+ * - Checking if a frame property is set in cases where that's all we want
+ * to know (i.e., we don't intend to read the actual value or remove the
+ * property).
+ *
+ * - Calling Has() before Set() in cases where we don't want to overwrite
+ * an existing value for the frame property.
+ */
+ template <typename T>
+ bool Has(Descriptor<T> aProperty) const {
+ return mProperties.Contains(aProperty, PropertyComparator());
+ }
+
+ /**
+ * Get a property value. This requires a linear search through
+ * the properties of the frame. If the frame has no such property,
+ * returns zero-filled result, which means null for pointers and
+ * zero for integers and floating point types.
+ * @param aFoundResult if non-null, receives a value 'true' iff
+ * the frame has a value for the property. This lets callers
+ * disambiguate a null result, which can mean 'no such property' or
+ * 'property value is null'.
+ */
+ template <typename T>
+ PropertyType<T> Get(Descriptor<T> aProperty,
+ bool* aFoundResult = nullptr) const {
+ uint64_t v = GetInternal(aProperty, aFoundResult);
+ return ReinterpretHelper<T>::FromInternalValue(v);
+ }
+
+ /**
+ * Remove a property value, and return it without destroying it.
+ *
+ * This requires a linear search through the properties of the frame.
+ * If the frame has no such property, returns zero-filled result, which means
+ * null for pointers and zero for integers and floating point types.
+ * @param aFoundResult if non-null, receives a value 'true' iff
+ * the frame had a value for the property. This lets callers
+ * disambiguate a null result, which can mean 'no such property' or
+ * 'property value is null'.
+ */
+ template <typename T>
+ PropertyType<T> Take(Descriptor<T> aProperty, bool* aFoundResult = nullptr) {
+ uint64_t v = TakeInternal(aProperty, aFoundResult);
+ return ReinterpretHelper<T>::FromInternalValue(v);
+ }
+
+ /**
+ * Remove and destroy a property value. This requires a linear search through
+ * the properties of the frame. If the frame has no such property, nothing
+ * happens.
+ */
+ template <typename T>
+ void Remove(Descriptor<T> aProperty, const nsIFrame* aFrame) {
+ RemoveInternal(aProperty, aFrame);
+ }
+
+ /**
+ * Call @aFunction for each property or until @aFunction returns false.
+ */
+ template <class F>
+ void ForEach(F aFunction) const {
+#ifdef DEBUG
+ size_t len = mProperties.Length();
+#endif
+ for (const auto& prop : mProperties) {
+ bool shouldContinue = aFunction(prop.mProperty, prop.mValue);
+ MOZ_ASSERT(len == mProperties.Length(),
+ "frame property list was modified by ForEach callback!");
+ if (!shouldContinue) {
+ return;
+ }
+ }
+ }
+
+ /**
+ * Remove and destroy all property values for the frame.
+ */
+ void RemoveAll(const nsIFrame* aFrame) {
+ nsTArray<PropertyValue> toDelete = std::move(mProperties);
+ for (auto& prop : toDelete) {
+ prop.DestroyValueFor(aFrame);
+ }
+ MOZ_ASSERT(mProperties.IsEmpty(), "a property dtor added new properties");
+ }
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ // We currently report only the shallow size of the mProperties array.
+ // As for the PropertyValue entries: we don't need to measure the mProperty
+ // field of because it always points to static memory, and we can't measure
+ // mValue because the type is opaque.
+ // XXX Can we do better, e.g. with a method on the descriptor?
+ return mProperties.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ private:
+ // Prevent copying of FrameProperties; we should always return/pass around
+ // references to it, not copies!
+ FrameProperties(const FrameProperties&) = delete;
+ FrameProperties& operator=(const FrameProperties&) = delete;
+
+ inline void SetInternal(UntypedDescriptor aProperty, uint64_t aValue,
+ const nsIFrame* aFrame);
+
+ inline void AddInternal(UntypedDescriptor aProperty, uint64_t aValue);
+
+ inline uint64_t GetInternal(UntypedDescriptor aProperty,
+ bool* aFoundResult) const;
+
+ inline uint64_t TakeInternal(UntypedDescriptor aProperty, bool* aFoundResult);
+
+ inline void RemoveInternal(UntypedDescriptor aProperty,
+ const nsIFrame* aFrame);
+
+ template <typename T>
+ struct ReinterpretHelper {
+ static_assert(sizeof(PropertyType<T>) <= sizeof(uint64_t),
+ "size of the value must never be larger than 64 bits");
+
+ static uint64_t ToInternalValue(PropertyType<T> aValue) {
+ uint64_t v = 0;
+ memcpy(&v, &aValue, sizeof(aValue));
+ return v;
+ }
+
+ static PropertyType<T> FromInternalValue(uint64_t aInternalValue) {
+ PropertyType<T> value;
+ memcpy(&value, &aInternalValue, sizeof(value));
+ return value;
+ }
+ };
+
+ /**
+ * Stores a property descriptor/value pair.
+ */
+ struct PropertyValue {
+ PropertyValue() : mProperty(nullptr), mValue(0) {}
+ PropertyValue(UntypedDescriptor aProperty, uint64_t aValue)
+ : mProperty(aProperty), mValue(aValue) {}
+
+ // NOTE: This function converts our internal 64-bit-integer representation
+ // to a pointer-type representation. This is lossy on 32-bit systems, but it
+ // should be fine, as long as we *only* do this in cases where we're sure
+ // that the stored property-value is in fact a pointer. And we should have
+ // that assurance, since only pointer-typed frame properties are expected to
+ // have a destructor
+ void DestroyValueFor(const nsIFrame* aFrame) {
+ if (mProperty->mDestructor) {
+ mProperty->mDestructor(
+ ReinterpretHelper<void*>::FromInternalValue(mValue));
+ } else if (mProperty->mDestructorWithFrame) {
+ mProperty->mDestructorWithFrame(
+ aFrame, ReinterpretHelper<void*>::FromInternalValue(mValue));
+ }
+ }
+
+ UntypedDescriptor mProperty;
+ uint64_t mValue;
+ };
+
+ /**
+ * Used with an array of PropertyValues to allow lookups that compare
+ * only on the FramePropertyDescriptor.
+ */
+ class PropertyComparator {
+ public:
+ bool Equals(const PropertyValue& a, const PropertyValue& b) const {
+ return a.mProperty == b.mProperty;
+ }
+ bool Equals(UntypedDescriptor a, const PropertyValue& b) const {
+ return a == b.mProperty;
+ }
+ bool Equals(const PropertyValue& a, UntypedDescriptor b) const {
+ return a.mProperty == b;
+ }
+ };
+
+ nsTArray<PropertyValue> mProperties;
+};
+
+inline uint64_t FrameProperties::GetInternal(UntypedDescriptor aProperty,
+ bool* aFoundResult) const {
+ MOZ_ASSERT(aProperty, "Null property?");
+
+ return mProperties.ApplyIf(
+ aProperty, 0, PropertyComparator(),
+ [&aFoundResult](const PropertyValue& aPV) -> uint64_t {
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+ return aPV.mValue;
+ },
+ [&aFoundResult]() -> uint64_t {
+ if (aFoundResult) {
+ *aFoundResult = false;
+ }
+ return 0;
+ });
+}
+
+inline void FrameProperties::SetInternal(UntypedDescriptor aProperty,
+ uint64_t aValue,
+ const nsIFrame* aFrame) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProperty, "Null property?");
+
+ mProperties.ApplyIf(
+ aProperty, 0, PropertyComparator(),
+ [&](PropertyValue& aPV) {
+ aPV.DestroyValueFor(aFrame);
+ aPV.mValue = aValue;
+ },
+ [&]() { mProperties.AppendElement(PropertyValue(aProperty, aValue)); });
+}
+
+inline void FrameProperties::AddInternal(UntypedDescriptor aProperty,
+ uint64_t aValue) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProperty, "Null property?");
+
+ mProperties.AppendElement(PropertyValue(aProperty, aValue));
+}
+
+inline uint64_t FrameProperties::TakeInternal(UntypedDescriptor aProperty,
+ bool* aFoundResult) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProperty, "Null property?");
+
+ auto index = mProperties.IndexOf(aProperty, 0, PropertyComparator());
+ if (index == nsTArray<PropertyValue>::NoIndex) {
+ if (aFoundResult) {
+ *aFoundResult = false;
+ }
+ return 0;
+ }
+
+ if (aFoundResult) {
+ *aFoundResult = true;
+ }
+
+ uint64_t result = mProperties.Elements()[index].mValue;
+ mProperties.RemoveElementAtUnsafe(index);
+
+ return result;
+}
+
+inline void FrameProperties::RemoveInternal(UntypedDescriptor aProperty,
+ const nsIFrame* aFrame) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aProperty, "Null property?");
+
+ auto index = mProperties.IndexOf(aProperty, 0, PropertyComparator());
+ if (index != nsTArray<PropertyValue>::NoIndex) {
+ mProperties.Elements()[index].DestroyValueFor(aFrame);
+ mProperties.RemoveElementAtUnsafe(index);
+ }
+}
+
+} // namespace mozilla
+
+#endif /* FRAMEPROPERTIES_H_ */
diff --git a/layout/base/GeckoMVMContext.cpp b/layout/base/GeckoMVMContext.cpp
new file mode 100644
index 0000000000..85bb21b85e
--- /dev/null
+++ b/layout/base/GeckoMVMContext.cpp
@@ -0,0 +1,217 @@
+/* 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 "GeckoMVMContext.h"
+
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/VisualViewport.h"
+#include "nsCOMPtr.h"
+#include "nsGlobalWindowInner.h"
+#include "nsIDOMEventListener.h"
+#include "nsIFrame.h"
+#include "nsIObserverService.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+GeckoMVMContext::GeckoMVMContext(dom::Document* aDocument,
+ PresShell* aPresShell)
+ : mDocument(aDocument), mPresShell(aPresShell) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ mEventTarget = window->GetChromeEventHandler();
+ }
+}
+
+void GeckoMVMContext::AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture) {
+ if (mEventTarget) {
+ mEventTarget->AddEventListener(aType, aListener, aUseCapture);
+ }
+}
+
+void GeckoMVMContext::RemoveEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture) {
+ if (mEventTarget) {
+ mEventTarget->RemoveEventListener(aType, aListener, aUseCapture);
+ }
+}
+
+void GeckoMVMContext::AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) {
+ if (nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService()) {
+ observerService->AddObserver(aObserver, aTopic, aOwnsWeak);
+ }
+}
+
+void GeckoMVMContext::RemoveObserver(nsIObserver* aObserver,
+ const char* aTopic) {
+ if (nsCOMPtr<nsIObserverService> observerService =
+ services::GetObserverService()) {
+ observerService->RemoveObserver(aObserver, aTopic);
+ }
+}
+
+void GeckoMVMContext::Destroy() {
+ mEventTarget = nullptr;
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+nsViewportInfo GeckoMVMContext::GetViewportInfo(
+ const ScreenIntSize& aDisplaySize) const {
+ MOZ_ASSERT(mDocument);
+ return mDocument->GetViewportInfo(aDisplaySize);
+}
+
+CSSToLayoutDeviceScale GeckoMVMContext::CSSToDevPixelScale() const {
+ MOZ_ASSERT(mPresShell);
+ return mPresShell->GetPresContext()->CSSToDevPixelScale();
+}
+
+float GeckoMVMContext::GetResolution() const {
+ MOZ_ASSERT(mPresShell);
+ return mPresShell->GetResolution();
+}
+
+bool GeckoMVMContext::SubjectMatchesDocument(nsISupports* aSubject) const {
+ MOZ_ASSERT(mDocument);
+ return SameCOMIdentity(aSubject, ToSupports(mDocument));
+}
+
+Maybe<CSSRect> GeckoMVMContext::CalculateScrollableRectForRSF() const {
+ MOZ_ASSERT(mPresShell);
+ if (nsIScrollableFrame* rootScrollableFrame =
+ mPresShell->GetRootScrollFrameAsScrollable()) {
+ return Some(
+ CSSRect::FromAppUnits(nsLayoutUtils::CalculateScrollableRectForFrame(
+ rootScrollableFrame, nullptr)));
+ }
+ return Nothing();
+}
+
+bool GeckoMVMContext::IsResolutionUpdatedByApz() const {
+ MOZ_ASSERT(mPresShell);
+ return mPresShell->IsResolutionUpdatedByApz();
+}
+
+LayoutDeviceMargin
+GeckoMVMContext::ScrollbarAreaToExcludeFromCompositionBounds() const {
+ MOZ_ASSERT(mPresShell);
+ return LayoutDeviceMargin::FromAppUnits(
+ nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
+ mPresShell->GetRootScrollFrame()),
+ mPresShell->GetPresContext()->AppUnitsPerDevPixel());
+}
+
+Maybe<LayoutDeviceIntSize> GeckoMVMContext::GetDocumentViewerSize() const {
+ MOZ_ASSERT(mPresShell);
+ LayoutDeviceIntSize result;
+ if (nsLayoutUtils::GetDocumentViewerSize(mPresShell->GetPresContext(),
+ result)) {
+ return Some(result);
+ }
+ return Nothing();
+}
+
+bool GeckoMVMContext::AllowZoomingForDocument() const {
+ MOZ_ASSERT(mDocument);
+ return nsLayoutUtils::AllowZoomingForDocument(mDocument);
+}
+
+bool GeckoMVMContext::IsInReaderMode() const {
+ MOZ_ASSERT(mDocument);
+ nsString uri;
+ if (NS_FAILED(mDocument->GetDocumentURI(uri))) {
+ return false;
+ }
+ static auto readerModeUriPrefix = u"about:reader"_ns;
+ return StringBeginsWith(uri, readerModeUriPrefix);
+}
+
+bool GeckoMVMContext::IsDocumentLoading() const {
+ MOZ_ASSERT(mDocument);
+ return mDocument->GetReadyStateEnum() == dom::Document::READYSTATE_LOADING;
+}
+
+void GeckoMVMContext::SetResolutionAndScaleTo(float aResolution,
+ ResolutionChangeOrigin aOrigin) {
+ MOZ_ASSERT(mPresShell);
+ mPresShell->SetResolutionAndScaleTo(aResolution, aOrigin);
+}
+
+void GeckoMVMContext::SetVisualViewportSize(const CSSSize& aSize) {
+ MOZ_ASSERT(mPresShell);
+ mPresShell->SetVisualViewportSize(
+ nsPresContext::CSSPixelsToAppUnits(aSize.width),
+ nsPresContext::CSSPixelsToAppUnits(aSize.height));
+}
+
+void GeckoMVMContext::PostVisualViewportResizeEventByDynamicToolbar() {
+ MOZ_ASSERT(mDocument);
+
+ // We only fire visual viewport events and don't want to cause any explicit
+ // reflows here since in general we don't use the up-to-date visual viewport
+ // size for layout.
+ if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+ window->VisualViewport()->PostResizeEvent();
+ }
+}
+
+void GeckoMVMContext::UpdateDisplayPortMargins() {
+ MOZ_ASSERT(mPresShell);
+ if (nsIFrame* root = mPresShell->GetRootScrollFrame()) {
+ nsIContent* content = root->GetContent();
+ bool hasDisplayPort = DisplayPortUtils::HasNonMinimalDisplayPort(content);
+ bool hasResolution = mPresShell->GetResolution() != 1.0f;
+ if (!hasDisplayPort && !hasResolution) {
+ // We only want to update the displayport if there is one already, or
+ // add one if there's a resolution on the document (see bug 1225508
+ // comment 1).
+ return;
+ }
+ nsRect displayportBase = nsRect(
+ nsPoint(0, 0), nsLayoutUtils::CalculateCompositionSizeForFrame(root));
+ // We only create MobileViewportManager for root content documents. If that
+ // ever changes we'd need to limit the size of this displayport base rect
+ // because non-toplevel documents have no limit on their size.
+ MOZ_ASSERT(
+ mPresShell->GetPresContext()->IsRootContentDocumentCrossProcess());
+ DisplayPortUtils::SetDisplayPortBaseIfNotSet(content, displayportBase);
+ nsIScrollableFrame* scrollable = do_QueryFrame(root);
+ DisplayPortUtils::CalculateAndSetDisplayPortMargins(
+ scrollable, DisplayPortUtils::RepaintMode::Repaint);
+ }
+}
+
+void GeckoMVMContext::Reflow(const CSSSize& aNewSize) {
+ RefPtr doc = mDocument;
+ RefPtr ps = mPresShell;
+
+ MOZ_ASSERT(doc);
+ MOZ_ASSERT(ps);
+
+ if (ps->ResizeReflowIgnoreOverride(CSSPixel::ToAppUnits(aNewSize.width),
+ CSSPixel::ToAppUnits(aNewSize.height))) {
+ doc->FlushPendingNotifications(FlushType::InterruptibleLayout);
+ }
+}
+
+ScreenIntCoord GeckoMVMContext::GetDynamicToolbarOffset() {
+ const nsPresContext* presContext = mPresShell->GetPresContext();
+ return presContext->HasDynamicToolbar()
+ ? presContext->GetDynamicToolbarMaxHeight() -
+ presContext->GetDynamicToolbarHeight()
+ : ScreenIntCoord(0);
+}
+
+} // namespace mozilla
diff --git a/layout/base/GeckoMVMContext.h b/layout/base/GeckoMVMContext.h
new file mode 100644
index 0000000000..fdbdcf22ae
--- /dev/null
+++ b/layout/base/GeckoMVMContext.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 GeckoMVMContext_h_
+#define GeckoMVMContext_h_
+
+#include "MVMContext.h"
+
+#include "mozilla/Attributes.h" // for MOZ_NON_OWNING_REF
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class EventTarget;
+} // namespace dom
+
+/**
+ * An implementation of MVMContext that uses actual Gecko components.
+ * This is intended for production use (whereas TestMVMContext is intended for
+ * testing.)
+ */
+class GeckoMVMContext final : public MVMContext {
+ public:
+ explicit GeckoMVMContext(dom::Document* aDocument, PresShell* aPresShell);
+ void AddEventListener(const nsAString& aType, nsIDOMEventListener* aListener,
+ bool aUseCapture) override;
+ void RemoveEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture) override;
+ void AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) override;
+ void RemoveObserver(nsIObserver* aObserver, const char* aTopic) override;
+ void Destroy() override;
+
+ nsViewportInfo GetViewportInfo(
+ const ScreenIntSize& aDisplaySize) const override;
+ CSSToLayoutDeviceScale CSSToDevPixelScale() const override;
+ float GetResolution() const override;
+ bool SubjectMatchesDocument(nsISupports* aSubject) const override;
+ Maybe<CSSRect> CalculateScrollableRectForRSF() const override;
+ bool IsResolutionUpdatedByApz() const override;
+ LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds()
+ const override;
+ Maybe<LayoutDeviceIntSize> GetDocumentViewerSize() const override;
+ bool AllowZoomingForDocument() const override;
+ bool IsInReaderMode() const override;
+ bool IsDocumentLoading() const override;
+
+ void SetResolutionAndScaleTo(float aResolution,
+ ResolutionChangeOrigin aOrigin) override;
+ void SetVisualViewportSize(const CSSSize& aSize) override;
+ void PostVisualViewportResizeEventByDynamicToolbar() override;
+ void UpdateDisplayPortMargins() override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void Reflow(const CSSSize& aNewSize) override;
+ ScreenIntCoord GetDynamicToolbarOffset() override;
+
+ private:
+ RefPtr<dom::Document> mDocument;
+ // raw ref since the presShell owns this
+ PresShell* MOZ_NON_OWNING_REF mPresShell;
+ nsCOMPtr<dom::EventTarget> mEventTarget;
+};
+
+} // namespace mozilla
+
+#endif // GeckoMVMContext_h_
diff --git a/layout/base/GeometryUtils.cpp b/layout/base/GeometryUtils.cpp
new file mode 100644
index 0000000000..dac899f755
--- /dev/null
+++ b/layout/base/GeometryUtils.cpp
@@ -0,0 +1,498 @@
+/* -*- 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 "GeometryUtils.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/DOMPointBinding.h"
+#include "mozilla/dom/GeometryUtilsBinding.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DOMPoint.h"
+#include "mozilla/dom/DOMQuad.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "nsIFrame.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+enum GeometryNodeType {
+ GEOMETRY_NODE_ELEMENT,
+ GEOMETRY_NODE_TEXT,
+ GEOMETRY_NODE_DOCUMENT
+};
+
+static nsIFrame* GetFrameForNode(nsINode* aNode, GeometryNodeType aType,
+ bool aCreateFramesForSuppressedWhitespace) {
+ Document* doc = aNode->OwnerDoc();
+ if (aType == GEOMETRY_NODE_TEXT && aCreateFramesForSuppressedWhitespace) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->FrameConstructor()->EnsureFrameForTextNodeIsCreatedAfterFlush(
+ static_cast<CharacterData*>(aNode));
+ }
+ }
+ doc->FlushPendingNotifications(FlushType::Layout);
+
+ switch (aType) {
+ case GEOMETRY_NODE_TEXT:
+ case GEOMETRY_NODE_ELEMENT:
+ return aNode->AsContent()->GetPrimaryFrame();
+ case GEOMETRY_NODE_DOCUMENT: {
+ PresShell* presShell = doc->GetPresShell();
+ return presShell ? presShell->GetRootFrame() : nullptr;
+ }
+ default:
+ MOZ_ASSERT(false, "Unknown GeometryNodeType");
+ return nullptr;
+ }
+}
+
+static nsIFrame* GetFrameForGeometryNode(
+ const Optional<OwningGeometryNode>& aGeometryNode, nsINode* aDefaultNode,
+ bool aCreateFramesForSuppressedWhitespace) {
+ if (!aGeometryNode.WasPassed()) {
+ return GetFrameForNode(aDefaultNode->OwnerDoc(), GEOMETRY_NODE_DOCUMENT,
+ aCreateFramesForSuppressedWhitespace);
+ }
+
+ const OwningGeometryNode& value = aGeometryNode.Value();
+ if (value.IsElement()) {
+ return GetFrameForNode(value.GetAsElement(), GEOMETRY_NODE_ELEMENT,
+ aCreateFramesForSuppressedWhitespace);
+ }
+ if (value.IsDocument()) {
+ return GetFrameForNode(value.GetAsDocument(), GEOMETRY_NODE_DOCUMENT,
+ aCreateFramesForSuppressedWhitespace);
+ }
+ return GetFrameForNode(value.GetAsText(), GEOMETRY_NODE_TEXT,
+ aCreateFramesForSuppressedWhitespace);
+}
+
+static nsIFrame* GetFrameForGeometryNode(const GeometryNode& aGeometryNode) {
+ // This will create frames for suppressed whitespace nodes.
+ if (aGeometryNode.IsElement()) {
+ return GetFrameForNode(&aGeometryNode.GetAsElement(), GEOMETRY_NODE_ELEMENT,
+ true);
+ }
+ if (aGeometryNode.IsDocument()) {
+ return GetFrameForNode(&aGeometryNode.GetAsDocument(),
+ GEOMETRY_NODE_DOCUMENT, true);
+ }
+ return GetFrameForNode(&aGeometryNode.GetAsText(), GEOMETRY_NODE_TEXT, true);
+}
+
+static nsIFrame* GetFrameForNode(nsINode* aNode,
+ bool aCreateFramesForSuppressedWhitespace) {
+ if (aNode->IsElement()) {
+ return GetFrameForNode(aNode, GEOMETRY_NODE_ELEMENT,
+ aCreateFramesForSuppressedWhitespace);
+ }
+ if (aNode == aNode->OwnerDoc()) {
+ return GetFrameForNode(aNode, GEOMETRY_NODE_DOCUMENT,
+ aCreateFramesForSuppressedWhitespace);
+ }
+ NS_ASSERTION(aNode->IsText(), "Unknown node type");
+ return GetFrameForNode(aNode, GEOMETRY_NODE_TEXT,
+ aCreateFramesForSuppressedWhitespace);
+}
+
+static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
+ const Optional<OwningGeometryNode>& aNode, nsINode* aDefaultNode,
+ bool aCreateFramesForSuppressedWhitespace) {
+ nsIFrame* f = GetFrameForGeometryNode(aNode, aDefaultNode,
+ aCreateFramesForSuppressedWhitespace);
+ if (f) {
+ f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
+ }
+ return f;
+}
+
+static nsIFrame* GetFirstNonAnonymousFrameForGeometryNode(
+ const GeometryNode& aNode) {
+ // This will create frames for suppressed whitespace nodes.
+ nsIFrame* f = GetFrameForGeometryNode(aNode);
+ if (f) {
+ f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
+ }
+ return f;
+}
+
+static nsIFrame* GetFirstNonAnonymousFrameForNode(nsINode* aNode) {
+ // This will create frames for suppressed whitespace nodes.
+ nsIFrame* f = GetFrameForNode(aNode, true);
+ if (f) {
+ f = nsLayoutUtils::GetFirstNonAnonymousFrame(f);
+ }
+ return f;
+}
+
+/**
+ * This can modify aFrame to point to a different frame. This is needed to
+ * handle SVG, where SVG elements can only compute a rect that's valid with
+ * respect to the "outer SVG" frame.
+ */
+static nsRect GetBoxRectForFrame(nsIFrame** aFrame, CSSBoxType aType) {
+ nsRect r;
+ nsIFrame* f = SVGUtils::GetOuterSVGFrameAndCoveredRegion(*aFrame, &r);
+ if (f && f != *aFrame) {
+ // For non-outer SVG frames, the BoxType is ignored.
+ *aFrame = f;
+ return r;
+ }
+
+ f = *aFrame;
+ switch (aType) {
+ case CSSBoxType::Content:
+ r = f->GetContentRectRelativeToSelf();
+ break;
+ case CSSBoxType::Padding:
+ r = f->GetPaddingRectRelativeToSelf();
+ break;
+ case CSSBoxType::Border:
+ r = nsRect(nsPoint(0, 0), f->GetSize());
+ break;
+ case CSSBoxType::Margin:
+ r = f->GetMarginRectRelativeToSelf();
+ break;
+ default:
+ MOZ_ASSERT(false, "unknown box type");
+ return r;
+ }
+
+ return r;
+}
+
+class AccumulateQuadCallback : public nsLayoutUtils::BoxCallback {
+ public:
+ AccumulateQuadCallback(Document* aParentObject,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ nsIFrame* aRelativeToFrame,
+ const nsPoint& aRelativeToBoxTopLeft,
+ CSSBoxType aBoxType)
+ : mParentObject(ToSupports(aParentObject)),
+ mResult(aResult),
+ mRelativeToFrame(aRelativeToFrame),
+ mRelativeToBoxTopLeft(aRelativeToBoxTopLeft),
+ mBoxType(aBoxType) {
+ if (mBoxType == CSSBoxType::Margin) {
+ // Don't include the caption margin when computing margins for a
+ // table
+ mIncludeCaptionBoxForTable = false;
+ }
+ }
+
+ void AddBox(nsIFrame* aFrame) override {
+ nsIFrame* f = aFrame;
+ if (mBoxType == CSSBoxType::Margin && f->IsTableFrame()) {
+ // Margin boxes for table frames should be taken from the table wrapper
+ // frame, since that has the margin.
+ f = f->GetParent();
+ }
+ nsRect box = GetBoxRectForFrame(&f, mBoxType);
+ nsPoint appUnits[4] = {box.TopLeft(), box.TopRight(), box.BottomRight(),
+ box.BottomLeft()};
+ CSSPoint points[4];
+ for (uint32_t i = 0; i < 4; ++i) {
+ points[i] =
+ CSSPoint(nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].x),
+ nsPresContext::AppUnitsToFloatCSSPixels(appUnits[i].y));
+ }
+ nsLayoutUtils::TransformResult rv = nsLayoutUtils::TransformPoints(
+ RelativeTo{f}, RelativeTo{mRelativeToFrame}, 4, points);
+ if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ CSSPoint delta(
+ nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(mRelativeToBoxTopLeft.y));
+ for (uint32_t i = 0; i < 4; ++i) {
+ points[i] -= delta;
+ }
+ } else {
+ PodArrayZero(points);
+ }
+ mResult.AppendElement(new DOMQuad(mParentObject, points));
+ }
+
+ nsISupports* mParentObject;
+ nsTArray<RefPtr<DOMQuad> >& mResult;
+ nsIFrame* mRelativeToFrame;
+ nsPoint mRelativeToBoxTopLeft;
+ CSSBoxType mBoxType;
+};
+
+static nsPresContext* FindTopLevelPresContext(nsPresContext* aPC) {
+ bool isChrome = aPC->IsChrome();
+ nsPresContext* pc = aPC;
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent || parent->IsChrome() != isChrome) {
+ return pc;
+ }
+ pc = parent;
+ }
+}
+
+static bool CheckFramesInSameTopLevelBrowsingContext(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ CallerType aCallerType) {
+ nsPresContext* pc1 = aFrame1->PresContext();
+ nsPresContext* pc2 = aFrame2->PresContext();
+ if (pc1 == pc2) {
+ return true;
+ }
+ if (aCallerType == CallerType::System) {
+ return true;
+ }
+ if (FindTopLevelPresContext(pc1) == FindTopLevelPresContext(pc2)) {
+ return true;
+ }
+ return false;
+}
+
+void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad> >& aResult, CallerType aCallerType,
+ ErrorResult& aRv) {
+ nsIFrame* frame =
+ GetFrameForNode(aNode, aOptions.mCreateFramesForSuppressedWhitespace);
+ if (!frame) {
+ // No boxes to return
+ return;
+ }
+ AutoWeakFrame weakFrame(frame);
+ Document* ownerDoc = aNode->OwnerDoc();
+ nsIFrame* relativeToFrame = GetFirstNonAnonymousFrameForGeometryNode(
+ aOptions.mRelativeTo, ownerDoc,
+ aOptions.mCreateFramesForSuppressedWhitespace);
+ // The first frame might be destroyed now if the above call lead to an
+ // EnsureFrameForTextNode call. We need to get the first frame again
+ // when that happens and re-check it.
+ if (!weakFrame.IsAlive()) {
+ frame =
+ GetFrameForNode(aNode, aOptions.mCreateFramesForSuppressedWhitespace);
+ if (!frame) {
+ // No boxes to return
+ return;
+ }
+ }
+ if (!relativeToFrame) {
+ // XXXbz There's no spec for this.
+ return aRv.ThrowNotFoundError("No box to get quads relative to");
+ }
+ if (!CheckFramesInSameTopLevelBrowsingContext(frame, relativeToFrame,
+ aCallerType)) {
+ aRv.ThrowNotFoundError(
+ "Can't get quads relative to a box in a different toplevel browsing "
+ "context");
+ return;
+ }
+ // GetBoxRectForFrame can modify relativeToFrame so call it first.
+ nsPoint relativeToTopLeft =
+ GetBoxRectForFrame(&relativeToFrame, CSSBoxType::Border).TopLeft();
+ AccumulateQuadCallback callback(ownerDoc, aResult, relativeToFrame,
+ relativeToTopLeft, aOptions.mBox);
+
+ // Bug 1624653: Refactor this to get boxes in layer pixels, which we will
+ // then convert into CSS units.
+ nsLayoutUtils::GetAllInFlowBoxes(frame, &callback);
+}
+
+void GetBoxQuadsFromWindowOrigin(nsINode* aNode,
+ const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<DOMQuad> >& aResult,
+ ErrorResult& aRv) {
+ // We want the quads relative to the window. To do this, we ignore the
+ // provided aOptions.mRelativeTo and instead use the document node of
+ // the top-most in-process document. Later, we'll check if there is a
+ // browserChild associated with that document, and if so, transform the
+ // calculated quads with the browserChild's to-parent matrix, which
+ // will get us to top-level coordinates.
+ if (aOptions.mRelativeTo.WasPassed()) {
+ return aRv.ThrowNotSupportedError(
+ "Can't request quads in window origin space relative to another "
+ "node.");
+ }
+
+ // We're going to call GetBoxQuads with our parameters, but we supply
+ // a new BoxQuadOptions object that uses the top in-process document
+ // as the relativeTo target.
+ BoxQuadOptions bqo(aOptions);
+
+ RefPtr<Document> topInProcessDoc =
+ nsContentUtils::GetInProcessSubtreeRootDocument(aNode->OwnerDoc());
+
+ OwningGeometryNode ogn;
+ ogn.SetAsDocument() = topInProcessDoc;
+ bqo.mRelativeTo.Construct(ogn);
+
+ // Bug 1624653: Refactor this to get boxes in layer pixels, which we can
+ // transform directly with the GetChildToParentConversionMatrix below,
+ // and convert to CSS units as a final step.
+ GetBoxQuads(aNode, bqo, aResult, CallerType::System, aRv);
+ if (aRv.Failed()) {
+ return;
+ }
+
+ // Now we have aResult filled with DOMQuads with values relative to the
+ // top in-process document. See if topInProcessDoc is associated with a
+ // BrowserChild, and if it is, get its transformation matrix and use that
+ // to transform the DOMQuads in place to make them relative to the window
+ // origin.
+ nsIDocShell* docShell = topInProcessDoc->GetDocShell();
+ if (!docShell) {
+ return aRv.ThrowInvalidStateError(
+ "Returning untranslated quads because top in process document has "
+ "no docshell.");
+ }
+
+ BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
+ if (!browserChild) {
+ return;
+ }
+
+ nsPresContext* presContext = docShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+ int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ LayoutDeviceToLayoutDeviceMatrix4x4 matrix =
+ browserChild->GetChildToParentConversionMatrix();
+
+ // For each DOMQuad in aResult, change the css units into layer pixels,
+ // then transform them by matrix, then change them back into css units
+ // and overwrite the original points.
+ LayoutDeviceToCSSScale ld2cScale((float)appUnitsPerDevPixel /
+ (float)AppUnitsPerCSSPixel());
+ CSSToLayoutDeviceScale c2ldScale = ld2cScale.Inverse();
+
+ for (auto& quad : aResult) {
+ for (uint32_t i = 0; i < 4; i++) {
+ DOMPoint* p = quad->Point(i);
+ CSSPoint cp(p->X(), p->Y());
+
+ LayoutDevicePoint windowLdp = matrix.TransformPoint(cp * c2ldScale);
+
+ CSSPoint windowCp = windowLdp * ld2cScale;
+ p->SetX(windowCp.x);
+ p->SetY(windowCp.y);
+ }
+ }
+}
+
+static void TransformPoints(nsINode* aTo, const GeometryNode& aFrom,
+ uint32_t aPointCount, CSSPoint* aPoints,
+ const ConvertCoordinateOptions& aOptions,
+ CallerType aCallerType, ErrorResult& aRv) {
+ nsIFrame* fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
+ AutoWeakFrame weakFrame(fromFrame);
+ nsIFrame* toFrame = GetFirstNonAnonymousFrameForNode(aTo);
+ // The first frame might be destroyed now if the above call lead to an
+ // EnsureFrameForTextNode call. We need to get the first frame again
+ // when that happens.
+ if (fromFrame && !weakFrame.IsAlive()) {
+ fromFrame = GetFirstNonAnonymousFrameForGeometryNode(aFrom);
+ }
+ if (!fromFrame || !toFrame) {
+ aRv.ThrowNotFoundError(
+ "Can't transform coordinates between nonexistent boxes");
+ return;
+ }
+ if (!CheckFramesInSameTopLevelBrowsingContext(fromFrame, toFrame,
+ aCallerType)) {
+ aRv.ThrowNotFoundError(
+ "Can't transform coordinates between boxes in different toplevel "
+ "browsing contexts");
+ return;
+ }
+
+ nsPoint fromOffset =
+ GetBoxRectForFrame(&fromFrame, aOptions.mFromBox).TopLeft();
+ nsPoint toOffset = GetBoxRectForFrame(&toFrame, aOptions.mToBox).TopLeft();
+ CSSPoint fromOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(fromOffset.y));
+ for (uint32_t i = 0; i < aPointCount; ++i) {
+ aPoints[i] += fromOffsetGfx;
+ }
+ nsLayoutUtils::TransformResult rv = nsLayoutUtils::TransformPoints(
+ RelativeTo{fromFrame}, RelativeTo{toFrame}, aPointCount, aPoints);
+ if (rv == nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ CSSPoint toOffsetGfx(nsPresContext::AppUnitsToFloatCSSPixels(toOffset.x),
+ nsPresContext::AppUnitsToFloatCSSPixels(toOffset.y));
+ for (uint32_t i = 0; i < aPointCount; ++i) {
+ aPoints[i] -= toOffsetGfx;
+ }
+ } else {
+ PodZero(aPoints, aPointCount);
+ }
+}
+
+already_AddRefed<DOMQuad> ConvertQuadFromNode(
+ nsINode* aTo, dom::DOMQuad& aQuad, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ CSSPoint points[4];
+ for (uint32_t i = 0; i < 4; ++i) {
+ DOMPoint* p = aQuad.Point(i);
+ if (p->W() != 1.0 || p->Z() != 0.0) {
+ aRv.ThrowInvalidStateError("Point is not 2d");
+ return nullptr;
+ }
+ points[i] = CSSPoint(p->X(), p->Y());
+ }
+ TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
+ return result.forget();
+}
+
+already_AddRefed<DOMQuad> ConvertRectFromNode(
+ nsINode* aTo, dom::DOMRectReadOnly& aRect, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ CSSPoint points[4];
+ double x = aRect.X(), y = aRect.Y(), w = aRect.Width(), h = aRect.Height();
+ points[0] = CSSPoint(x, y);
+ points[1] = CSSPoint(x + w, y);
+ points[2] = CSSPoint(x + w, y + h);
+ points[3] = CSSPoint(x, y + h);
+ TransformPoints(aTo, aFrom, 4, points, aOptions, aCallerType, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMQuad> result = new DOMQuad(aTo->GetParentObject().mObject, points);
+ return result.forget();
+}
+
+already_AddRefed<DOMPoint> ConvertPointFromNode(
+ nsINode* aTo, const dom::DOMPointInit& aPoint, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, CallerType aCallerType,
+ ErrorResult& aRv) {
+ if (aPoint.mW != 1.0 || aPoint.mZ != 0.0) {
+ aRv.ThrowInvalidStateError("Point is not 2d");
+ return nullptr;
+ }
+ CSSPoint point(aPoint.mX, aPoint.mY);
+ TransformPoints(aTo, aFrom, 1, &point, aOptions, aCallerType, aRv);
+ if (aRv.Failed()) {
+ return nullptr;
+ }
+ RefPtr<DOMPoint> result =
+ new DOMPoint(aTo->GetParentObject().mObject, point.x, point.y);
+ return result.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/base/GeometryUtils.h b/layout/base/GeometryUtils.h
new file mode 100644
index 0000000000..eabbad5305
--- /dev/null
+++ b/layout/base/GeometryUtils.h
@@ -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/. */
+
+#ifndef MOZILLA_GEOMETRYUTILS_H_
+#define MOZILLA_GEOMETRYUTILS_H_
+
+#include "nsTArray.h"
+#include "nsCOMPtr.h"
+
+/**
+ * This file defines utility functions for converting between layout
+ * coordinate systems.
+ */
+
+class nsINode;
+
+namespace mozilla {
+class ErrorResult;
+
+namespace dom {
+struct BoxQuadOptions;
+struct ConvertCoordinateOptions;
+class DOMQuad;
+class DOMRectReadOnly;
+class DOMPoint;
+struct DOMPointInit;
+class OwningTextOrElementOrDocument;
+class TextOrElementOrDocument;
+enum class CallerType : uint32_t;
+} // namespace dom
+
+typedef dom::TextOrElementOrDocument GeometryNode;
+typedef dom::OwningTextOrElementOrDocument OwningGeometryNode;
+
+/**
+ * Computes quads for aNode using aOptions, according to
+ * GeometryUtils.getBoxQuads. May set an error in aRv.
+ */
+void GetBoxQuads(nsINode* aNode, const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<dom::DOMQuad> >& aResult,
+ dom::CallerType aCallerType, ErrorResult& aRv);
+
+void GetBoxQuadsFromWindowOrigin(nsINode* aNode,
+ const dom::BoxQuadOptions& aOptions,
+ nsTArray<RefPtr<dom::DOMQuad> >& aResult,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMQuad> ConvertQuadFromNode(
+ nsINode* aTo, dom::DOMQuad& aQuad, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, dom::CallerType aCallerType,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMQuad> ConvertRectFromNode(
+ nsINode* aTo, dom::DOMRectReadOnly& aRect, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, dom::CallerType aCallerType,
+ ErrorResult& aRv);
+
+already_AddRefed<dom::DOMPoint> ConvertPointFromNode(
+ nsINode* aTo, const dom::DOMPointInit& aPoint, const GeometryNode& aFrom,
+ const dom::ConvertCoordinateOptions& aOptions, dom::CallerType aCallerType,
+ ErrorResult& aRv);
+
+} // namespace mozilla
+
+#endif /* MOZILLA_GEOMETRYUTILS_H_ */
diff --git a/layout/base/LayoutConstants.h b/layout/base/LayoutConstants.h
new file mode 100644
index 0000000000..e8c7bb4221
--- /dev/null
+++ b/layout/base/LayoutConstants.h
@@ -0,0 +1,108 @@
+/* -*- 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 used throughout the Layout module */
+
+#ifndef LayoutConstants_h___
+#define LayoutConstants_h___
+
+#include "mozilla/EnumSet.h"
+#include "Units.h"
+
+/**
+ * Constant used to indicate an unconstrained size.
+ *
+ * NOTE: The constants defined in this file are semantically used as symbolic
+ * values, so user should not depend on the underlying numeric values. If
+ * new specific use cases arise, define a new constant here.
+ */
+inline constexpr nscoord NS_UNCONSTRAINEDSIZE = nscoord_MAX;
+
+// NS_AUTOOFFSET is assumed to have the same value as NS_UNCONSTRAINEDSIZE.
+inline constexpr nscoord NS_AUTOOFFSET = NS_UNCONSTRAINEDSIZE;
+
+// +1 is to avoid clamped huge margin values being processed as auto margins
+inline constexpr nscoord NS_AUTOMARGIN = NS_UNCONSTRAINEDSIZE + 1;
+
+inline constexpr nscoord NS_INTRINSIC_ISIZE_UNKNOWN = nscoord_MIN;
+
+namespace mozilla {
+
+/**
+ * Bit-flags to pass to various functions that compute sizes like
+ * nsIFrame::ComputeSize().
+ */
+enum class ComputeSizeFlag : uint8_t {
+ /**
+ * Set if the frame is in a context where non-replaced blocks should
+ * shrink-wrap (e.g., it's floating, absolutely positioned, or
+ * inline-block).
+ */
+ ShrinkWrap,
+
+ /**
+ * Set if this is a grid measuring reflow, to prevent stretching.
+ */
+ IsGridMeasuringReflow,
+
+ /**
+ * Indicates that we should clamp the margin-box min-size to the given CB
+ * size. This is used for implementing the grid area clamping here:
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ */
+ IClampMarginBoxMinSize, // clamp in our inline axis
+ BClampMarginBoxMinSize, // clamp in our block axis
+
+ /**
+ * The frame is stretching (per CSS Box Alignment) and doesn't have an
+ * Automatic Minimum Size in the indicated axis.
+ * (may be used for both flex/grid items, but currently only used for Grid)
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ * https://drafts.csswg.org/css-align-3/#valdef-justify-self-stretch
+ */
+ IApplyAutoMinSize, // Only has an effect when the ShrinkWrap bit is unset.
+};
+using ComputeSizeFlags = mozilla::EnumSet<ComputeSizeFlag>;
+
+/**
+ * The fallback size of width is 300px and the aspect-ratio is 2:1, based on
+ * CSS2 section 10.3.2 and CSS Sizing Level 3 section 5.1:
+ * https://drafts.csswg.org/css2/visudet.html#inline-replaced-width
+ * https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
+ */
+inline constexpr CSSIntCoord kFallbackIntrinsicWidthInPixels(300);
+inline constexpr CSSIntCoord kFallbackIntrinsicHeightInPixels(150);
+inline constexpr CSSIntSize kFallbackIntrinsicSizeInPixels(
+ kFallbackIntrinsicWidthInPixels, kFallbackIntrinsicHeightInPixels);
+
+inline constexpr nscoord kFallbackIntrinsicWidth =
+ kFallbackIntrinsicWidthInPixels * AppUnitsPerCSSPixel();
+inline constexpr nscoord kFallbackIntrinsicHeight =
+ kFallbackIntrinsicHeightInPixels * AppUnitsPerCSSPixel();
+inline constexpr nsSize kFallbackIntrinsicSize(kFallbackIntrinsicWidth,
+ kFallbackIntrinsicHeight);
+
+/**
+ * This is used in some nsLayoutUtils functions.
+ * Declared here so that fewer files need to include nsLayoutUtils.h.
+ */
+enum class IntrinsicISizeType { MinISize, PrefISize };
+
+enum class ContentRelevancyReason {
+ // If the content of this Frame is on screen or nearly on screen.
+ Visible,
+
+ // If this Frame's element has focus in its subtree.
+ FocusInSubtree,
+
+ // If this Frame's content is part of a selection.
+ Selected,
+};
+using ContentRelevancy = EnumSet<ContentRelevancyReason, uint8_t>;
+
+} // namespace mozilla
+
+#endif // LayoutConstants_h___
diff --git a/layout/base/LayoutLogging.cpp b/layout/base/LayoutLogging.cpp
new file mode 100644
index 0000000000..a56fb91340
--- /dev/null
+++ b/layout/base/LayoutLogging.cpp
@@ -0,0 +1,29 @@
+/* -*- 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/. */
+
+// Chromium headers must come before Mozilla headers.
+#include "base/process_util.h"
+
+#include "LayoutLogging.h"
+
+namespace mozilla {
+namespace detail {
+
+void LayoutLogWarning(const char* aStr, const char* aExpr, const char* aFile,
+ int32_t aLine) {
+ if (aExpr) {
+ MOZ_LOG(sLayoutLog, mozilla::LogLevel::Warning,
+ ("[%" PRIPID "] WARNING: %s: '%s', file %s, line %d",
+ base::GetCurrentProcId(), aStr, aExpr, aFile, aLine));
+ } else {
+ MOZ_LOG(sLayoutLog, mozilla::LogLevel::Warning,
+ ("[%" PRIPID "] WARNING: %s: file %s, line %d",
+ base::GetCurrentProcId(), aStr, aFile, aLine));
+ }
+}
+
+} // namespace detail
+} // namespace mozilla
diff --git a/layout/base/LayoutLogging.h b/layout/base/LayoutLogging.h
new file mode 100644
index 0000000000..72335ff3eb
--- /dev/null
+++ b/layout/base/LayoutLogging.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 LayoutLogging_h
+#define LayoutLogging_h
+
+#include "mozilla/Logging.h"
+
+/**
+ * Retrieves the log module to use for layout logging.
+ */
+static mozilla::LazyLogModule sLayoutLog("layout");
+
+/**
+ * Use the layout log to warn if a given condition is false.
+ *
+ * This is only enabled in debug builds and the logging is only displayed if
+ * the environmental variable MOZ_LOG includes "layout:2" (or higher).
+ */
+#ifdef DEBUG
+# define LAYOUT_WARN_IF_FALSE(_cond, _msg) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(sLayoutLog, mozilla::LogLevel::Warning) && !(_cond)) { \
+ mozilla::detail::LayoutLogWarning(_msg, #_cond, __FILE__, __LINE__); \
+ } \
+ PR_END_MACRO
+#else
+# define LAYOUT_WARN_IF_FALSE(_cond, _msg) \
+ PR_BEGIN_MACRO \
+ PR_END_MACRO
+#endif
+
+/**
+ * Use the layout log to emit a warning with the same format as NS_WARNING.
+ *
+ * This is only enabled in debug builds and the logging is only displayed if
+ * the environmental variable MOZ_LOG includes "layout:2" (or higher).
+ */
+#ifdef DEBUG
+# define LAYOUT_WARNING(_msg) \
+ PR_BEGIN_MACRO \
+ if (MOZ_LOG_TEST(sLayoutLog, mozilla::LogLevel::Warning)) { \
+ mozilla::detail::LayoutLogWarning(_msg, nullptr, __FILE__, __LINE__); \
+ } \
+ PR_END_MACRO
+#else
+# define LAYOUT_WARNING(_msg) \
+ PR_BEGIN_MACRO \
+ PR_END_MACRO
+#endif
+
+namespace mozilla {
+namespace detail {
+
+void LayoutLogWarning(const char* aStr, const char* aExpr, const char* aFile,
+ int32_t aLine);
+
+} // namespace detail
+} // namespace mozilla
+
+#endif // LayoutLogging_h
diff --git a/layout/base/LayoutTelemetryTools.cpp b/layout/base/LayoutTelemetryTools.cpp
new file mode 100644
index 0000000000..b59b73f810
--- /dev/null
+++ b/layout/base/LayoutTelemetryTools.cpp
@@ -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/. */
+
+#include "mozilla/layout/LayoutTelemetryTools.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/Telemetry.h"
+
+using namespace mozilla;
+using namespace mozilla::layout_telemetry;
+
+// Returns the key name expected by telemetry. Keep to date with
+// toolkits/components/telemetry/Histograms.json.
+static nsLiteralCString SubsystemTelemetryKey(LayoutSubsystem aSubsystem) {
+ switch (aSubsystem) {
+ default:
+ MOZ_CRASH("Unexpected LayoutSubsystem value");
+ case LayoutSubsystem::Restyle:
+ return "Restyle"_ns;
+ case LayoutSubsystem::Reflow:
+ return "ReflowOther"_ns;
+ case LayoutSubsystem::ReflowFlex:
+ return "ReflowFlex"_ns;
+ case LayoutSubsystem::ReflowGrid:
+ return "ReflowGrid"_ns;
+ case LayoutSubsystem::ReflowTable:
+ return "ReflowTable"_ns;
+ case LayoutSubsystem::ReflowText:
+ return "ReflowText"_ns;
+ }
+}
+
+static AutoRecord* sCurrentRecord;
+
+static FlushKind ToKind(FlushType aFlushType) {
+ switch (aFlushType) {
+ default:
+ MOZ_CRASH("Expected FlushType::Style or FlushType::Layout");
+ case FlushType::Style:
+ return FlushKind::Style;
+ case FlushType::Layout:
+ return FlushKind::Layout;
+ }
+}
+
+namespace mozilla {
+namespace layout_telemetry {
+
+Data::Data() {
+ PodZero(&mReqsPerFlush);
+ PodZero(&mFlushesPerTick);
+ PodZero(&mLayoutSubsystemDurationMs);
+}
+
+void Data::IncReqsPerFlush(FlushType aFlushType) {
+ mReqsPerFlush[ToKind(aFlushType)]++;
+}
+
+void Data::IncFlushesPerTick(FlushType aFlushType) {
+ mFlushesPerTick[ToKind(aFlushType)]++;
+}
+
+void Data::PingReqsPerFlushTelemetry(FlushType aFlushType) {
+ auto flushKind = ToKind(aFlushType);
+ if (flushKind == FlushKind::Layout) {
+ auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
+ auto layoutFlushReqs = mReqsPerFlush[FlushKind::Layout].value();
+ Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
+ "Style"_ns, styleFlushReqs);
+ Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_LAYOUT_FLUSH,
+ "Layout"_ns, layoutFlushReqs);
+ mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
+ mReqsPerFlush[FlushKind::Layout] = SaturateUint8(0);
+ } else {
+ auto styleFlushReqs = mReqsPerFlush[FlushKind::Style].value();
+ Telemetry::Accumulate(Telemetry::PRESSHELL_REQS_PER_STYLE_FLUSH,
+ styleFlushReqs);
+ mReqsPerFlush[FlushKind::Style] = SaturateUint8(0);
+ }
+}
+
+void Data::PingFlushPerTickTelemetry(FlushType aFlushType) {
+ auto flushKind = ToKind(aFlushType);
+ auto styleFlushes = mFlushesPerTick[FlushKind::Style].value();
+ if (styleFlushes > 0) {
+ Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK, "Style"_ns,
+ styleFlushes);
+ mFlushesPerTick[FlushKind::Style] = SaturateUint8(0);
+ }
+
+ auto layoutFlushes = mFlushesPerTick[FlushKind::Layout].value();
+ if (flushKind == FlushKind::Layout && layoutFlushes > 0) {
+ Telemetry::Accumulate(Telemetry::PRESSHELL_FLUSHES_PER_TICK, "Layout"_ns,
+ layoutFlushes);
+ mFlushesPerTick[FlushKind::Layout] = SaturateUint8(0);
+ }
+}
+
+void Data::PingTotalMsPerTickTelemetry(FlushType aFlushType) {
+ auto flushKind = ToKind(aFlushType);
+ auto range = (flushKind == FlushKind::Style)
+ ? MakeEnumeratedRange(LayoutSubsystem::Restyle,
+ LayoutSubsystem::Reflow)
+ : MakeEnumeratedRange(LayoutSubsystem::Reflow,
+ LayoutSubsystem::Count);
+
+ for (auto subsystem : range) {
+ auto key = SubsystemTelemetryKey(subsystem);
+ double& duration = mLayoutSubsystemDurationMs[subsystem];
+ if (duration > 0.0) {
+ Telemetry::Accumulate(Telemetry::PRESSHELL_LAYOUT_TOTAL_MS_PER_TICK, key,
+ static_cast<uint32_t>(duration));
+ duration = 0.0;
+ }
+ }
+}
+
+void Data::PingPerTickTelemetry(FlushType aFlushType) {
+ PingFlushPerTickTelemetry(aFlushType);
+ PingTotalMsPerTickTelemetry(aFlushType);
+}
+
+AutoRecord::AutoRecord(LayoutSubsystem aSubsystem)
+ : AutoRecord(nullptr, aSubsystem) {}
+
+AutoRecord::AutoRecord(Data* aLayoutTelemetry, LayoutSubsystem aSubsystem)
+ : mParentRecord(sCurrentRecord),
+ mLayoutTelemetry(aLayoutTelemetry),
+ mSubsystem(aSubsystem),
+ mStartTime(TimeStamp::Now()),
+ mDurationMs(0.0) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // If we're re-entering the same subsystem, don't update the current record.
+ if (mParentRecord) {
+ if (mParentRecord->mSubsystem == mSubsystem) {
+ return;
+ }
+
+ mLayoutTelemetry = mParentRecord->mLayoutTelemetry;
+ MOZ_ASSERT(mLayoutTelemetry);
+
+ // If we're entering a new subsystem, record the amount of time spent in the
+ // parent record before setting the new current record.
+ mParentRecord->mDurationMs +=
+ (mStartTime - mParentRecord->mStartTime).ToMilliseconds();
+ }
+
+ sCurrentRecord = this;
+}
+
+AutoRecord::~AutoRecord() {
+ if (sCurrentRecord != this) {
+ // If this record is not head of the list, do nothing.
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ mDurationMs += (now - mStartTime).ToMilliseconds();
+ mLayoutTelemetry->mLayoutSubsystemDurationMs[mSubsystem] += mDurationMs;
+
+ if (mParentRecord) {
+ // Restart the parent recording from this point
+ mParentRecord->mStartTime = now;
+ }
+
+ // Unlink this record from the current record list
+ sCurrentRecord = mParentRecord;
+}
+
+} // namespace layout_telemetry
+} // namespace mozilla
diff --git a/layout/base/LayoutTelemetryTools.h b/layout/base/LayoutTelemetryTools.h
new file mode 100644
index 0000000000..25c80dcc2c
--- /dev/null
+++ b/layout/base/LayoutTelemetryTools.h
@@ -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/. */
+
+/* Tools for collecting and reporting layout and style telemetry */
+
+#ifndef mozilla_LayoutTelemetryTools_h
+#define mozilla_LayoutTelemetryTools_h
+
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/EnumeratedRange.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/TimeStamp.h"
+
+#define LAYOUT_TELEMETRY_RECORD(subsystem) \
+ layout_telemetry::AutoRecord a(layout_telemetry::LayoutSubsystem::subsystem)
+
+#define LAYOUT_TELEMETRY_RECORD_BASE(subsystem) \
+ layout_telemetry::AutoRecord a(&mLayoutTelemetry, \
+ layout_telemetry::LayoutSubsystem::subsystem)
+
+namespace mozilla {
+namespace layout_telemetry {
+
+enum class FlushKind : uint8_t { Style, Layout, Count };
+
+enum class LayoutSubsystem : uint8_t {
+ Restyle,
+ Reflow,
+ ReflowFlex,
+ ReflowGrid,
+ ReflowTable,
+ ReflowText,
+ Count
+};
+
+using LayoutSubsystemDurations =
+ EnumeratedArray<LayoutSubsystem, LayoutSubsystem::Count, double>;
+using LayoutFlushCount =
+ EnumeratedArray<FlushKind, FlushKind::Count, SaturateUint8>;
+
+struct Data {
+ Data();
+
+ void IncReqsPerFlush(FlushType aFlushType);
+ void IncFlushesPerTick(FlushType aFlushType);
+
+ void PingTelemetry();
+
+ // Send the current number of flush requests for aFlushType to telemetry and
+ // reset the count.
+ void PingReqsPerFlushTelemetry(FlushType aFlushType);
+
+ // Send the current non-zero number of style and layout flushes to telemetry
+ // and reset the count.
+ void PingFlushPerTickTelemetry(FlushType aFlushType);
+
+ // Send the current non-zero time spent under style and layout processing this
+ // tick to telemetry and reset the total.
+ void PingTotalMsPerTickTelemetry(FlushType aFlushType);
+
+ // Send the current per-tick telemetry for `aFlushType`.
+ void PingPerTickTelemetry(FlushType aFlushType);
+
+ LayoutFlushCount mReqsPerFlush;
+ LayoutFlushCount mFlushesPerTick;
+ LayoutSubsystemDurations mLayoutSubsystemDurationMs;
+};
+
+class AutoRecord {
+ public:
+ explicit AutoRecord(LayoutSubsystem aSubsystem);
+ AutoRecord(Data* aLayoutTelemetry, LayoutSubsystem aSubsystem);
+ ~AutoRecord();
+
+ private:
+ AutoRecord* mParentRecord;
+ Data* mLayoutTelemetry;
+ LayoutSubsystem mSubsystem;
+ TimeStamp mStartTime;
+ double mDurationMs;
+};
+
+} // namespace layout_telemetry
+} // namespace mozilla
+
+#endif // !mozilla_LayoutTelemetryTools_h
diff --git a/layout/base/MVMContext.h b/layout/base/MVMContext.h
new file mode 100644
index 0000000000..bf5ae09fcd
--- /dev/null
+++ b/layout/base/MVMContext.h
@@ -0,0 +1,69 @@
+/* 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 MVMContext_h_
+#define MVMContext_h_
+
+#include "Units.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShellForwards.h"
+#include "nsISupportsImpl.h"
+#include "nsStringFwd.h"
+#include "nsViewportInfo.h"
+
+class nsIDOMEventListener;
+class nsIObserver;
+class nsISupports;
+
+namespace mozilla {
+
+/**
+ * The interface MobileViewportManager uses to interface with its surroundings.
+ * This mainly exists to facilitate testing MobileViewportManager in isolation
+ * from the rest of Gecko.
+ */
+class MVMContext {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MVMContext)
+ protected:
+ virtual ~MVMContext() {}
+
+ public:
+ virtual void AddEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture) = 0;
+ virtual void RemoveEventListener(const nsAString& aType,
+ nsIDOMEventListener* aListener,
+ bool aUseCapture) = 0;
+ virtual void AddObserver(nsIObserver* aObserver, const char* aTopic,
+ bool aOwnsWeak) = 0;
+ virtual void RemoveObserver(nsIObserver* aObserver, const char* aTopic) = 0;
+ virtual void Destroy() = 0;
+
+ virtual nsViewportInfo GetViewportInfo(
+ const ScreenIntSize& aDisplaySize) const = 0;
+ virtual CSSToLayoutDeviceScale CSSToDevPixelScale() const = 0;
+ virtual float GetResolution() const = 0;
+ virtual bool SubjectMatchesDocument(nsISupports* aSubject) const = 0;
+ virtual Maybe<CSSRect> CalculateScrollableRectForRSF() const = 0;
+ virtual bool IsResolutionUpdatedByApz() const = 0;
+ virtual LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds()
+ const = 0;
+ virtual Maybe<LayoutDeviceIntSize> GetDocumentViewerSize() const = 0;
+ virtual bool AllowZoomingForDocument() const = 0;
+ virtual bool IsInReaderMode() const = 0;
+ virtual bool IsDocumentLoading() const = 0;
+
+ virtual void SetResolutionAndScaleTo(float aResolution,
+ ResolutionChangeOrigin aOrigin) = 0;
+ virtual void SetVisualViewportSize(const CSSSize& aSize) = 0;
+ virtual void PostVisualViewportResizeEventByDynamicToolbar() = 0;
+ virtual void UpdateDisplayPortMargins() = 0;
+
+ virtual void Reflow(const CSSSize& aNewSize) = 0;
+ virtual ScreenIntCoord GetDynamicToolbarOffset() = 0;
+};
+
+} // namespace mozilla
+
+#endif // MVMContext_h_
diff --git a/layout/base/MediaEmulationData.h b/layout/base/MediaEmulationData.h
new file mode 100644
index 0000000000..7d4cf4d0d2
--- /dev/null
+++ b/layout/base/MediaEmulationData.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/. */
+
+/* Common data for media query emulation by DevTools, hanging off nsPresContext
+ */
+
+#ifndef mozilla_MediaEmulationData_h
+#define mozilla_MediaEmulationData_h
+
+#include "nsAtom.h"
+
+namespace mozilla {
+
+struct MediaEmulationData final {
+ MediaEmulationData() = default;
+
+ RefPtr<nsAtom> mMedium;
+ float mDPPX = 0.0;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/MobileViewportManager.cpp b/layout/base/MobileViewportManager.cpp
new file mode 100644
index 0000000000..0dcb57bd26
--- /dev/null
+++ b/layout/base/MobileViewportManager.cpp
@@ -0,0 +1,757 @@
+/* -*- 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 "MobileViewportManager.h"
+
+#include "mozilla/PresShell.h"
+#include "mozilla/ToString.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/EventTarget.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+#include "nsViewportInfo.h"
+#include "UnitTransforms.h"
+
+mozilla::LazyLogModule MobileViewportManager::gLog("apz.mobileviewport");
+#define MVM_LOG(...) \
+ MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug, (__VA_ARGS__))
+
+NS_IMPL_ISUPPORTS(MobileViewportManager, nsIDOMEventListener, nsIObserver)
+
+#define DOM_META_ADDED u"DOMMetaAdded"_ns
+#define DOM_META_CHANGED u"DOMMetaChanged"_ns
+#define FULLSCREEN_CHANGED u"fullscreenchange"_ns
+#define LOAD u"load"_ns
+#define BEFORE_FIRST_PAINT "before-first-paint"_ns
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+MobileViewportManager::MobileViewportManager(MVMContext* aContext,
+ ManagerType aType)
+ : mContext(aContext),
+ mManagerType(aType),
+ mIsFirstPaint(false),
+ mPainted(false) {
+ MOZ_ASSERT(mContext);
+
+ MVM_LOG("%p: creating with context %p\n", this, mContext.get());
+
+ mContext->AddEventListener(DOM_META_ADDED, this, false);
+ mContext->AddEventListener(DOM_META_CHANGED, this, false);
+ mContext->AddEventListener(FULLSCREEN_CHANGED, this, false);
+ mContext->AddEventListener(LOAD, this, true);
+
+ mContext->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
+
+ // We need to initialize the display size and the CSS viewport size before
+ // the initial reflow happens.
+ UpdateSizesBeforeReflow();
+}
+
+MobileViewportManager::~MobileViewportManager() = default;
+
+void MobileViewportManager::Destroy() {
+ MVM_LOG("%p: destroying\n", this);
+
+ mContext->RemoveEventListener(DOM_META_ADDED, this, false);
+ mContext->RemoveEventListener(DOM_META_CHANGED, this, false);
+ mContext->RemoveEventListener(FULLSCREEN_CHANGED, this, false);
+ mContext->RemoveEventListener(LOAD, this, true);
+
+ mContext->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
+
+ mContext->Destroy();
+ mContext = nullptr;
+}
+
+void MobileViewportManager::SetRestoreResolution(
+ float aResolution, LayoutDeviceIntSize aDisplaySize) {
+ SetRestoreResolution(aResolution);
+ ScreenIntSize restoreDisplaySize = ViewAs<ScreenPixel>(
+ aDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ mRestoreDisplaySize = Some(restoreDisplaySize);
+}
+
+void MobileViewportManager::SetRestoreResolution(float aResolution) {
+ mRestoreResolution = Some(aResolution);
+}
+
+float MobileViewportManager::ComputeIntrinsicResolution() const {
+ if (!mContext) {
+ return 1.f;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ CSSToScreenScale intrinsicScale = ComputeIntrinsicScale(
+ mContext->GetViewportInfo(displaySize), displaySize, mMobileViewportSize);
+ CSSToLayoutDeviceScale cssToDev = mContext->CSSToDevPixelScale();
+ return (intrinsicScale / cssToDev).scale;
+}
+
+mozilla::CSSToScreenScale MobileViewportManager::ComputeIntrinsicScale(
+ const nsViewportInfo& aViewportInfo,
+ const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSSize& aViewportOrContentSize) const {
+ CSSToScreenScale intrinsicScale =
+ aViewportOrContentSize.IsEmpty()
+ ? CSSToScreenScale(1.0)
+ : MaxScaleRatio(ScreenSize(aDisplaySize), aViewportOrContentSize);
+ MVM_LOG("%p: Intrinsic computed zoom is %f\n", this, intrinsicScale.scale);
+ return ClampZoom(intrinsicScale, aViewportInfo);
+}
+
+void MobileViewportManager::RequestReflow(bool aForceAdjustResolution) {
+ MVM_LOG("%p: got a reflow request with force resolution: %d\n", this,
+ aForceAdjustResolution);
+ RefreshViewportSize(aForceAdjustResolution);
+}
+
+void MobileViewportManager::ResolutionUpdated(
+ mozilla::ResolutionChangeOrigin aOrigin) {
+ MVM_LOG("%p: resolution updated\n", this);
+
+ if (!mContext) {
+ return;
+ }
+
+ if ((!mPainted &&
+ aOrigin == mozilla::ResolutionChangeOrigin::MainThreadRestore) ||
+ aOrigin == mozilla::ResolutionChangeOrigin::Test) {
+ // Save the value, so our default zoom calculation
+ // can take it into account later on.
+ SetRestoreResolution(mContext->GetResolution());
+ }
+ RefreshVisualViewportSize();
+}
+
+NS_IMETHODIMP
+MobileViewportManager::HandleEvent(dom::Event* event) {
+ nsAutoString type;
+ event->GetType(type);
+
+ if (type.Equals(DOM_META_ADDED)) {
+ HandleDOMMetaAdded();
+ } else if (type.Equals(DOM_META_CHANGED)) {
+ MVM_LOG("%p: got a dom-meta-changed event\n", this);
+ RefreshViewportSize(mPainted);
+ } else if (type.Equals(FULLSCREEN_CHANGED)) {
+ MVM_LOG("%p: got a fullscreenchange event\n", this);
+ RefreshViewportSize(mPainted);
+ } else if (type.Equals(LOAD)) {
+ MVM_LOG("%p: got a load event\n", this);
+ if (!mPainted) {
+ // Load event got fired before the before-first-paint message
+ SetInitialViewport();
+ }
+ }
+ return NS_OK;
+}
+
+void MobileViewportManager::HandleDOMMetaAdded() {
+ MVM_LOG("%p: got a dom-meta-added event\n", this);
+ if (mPainted && mContext->IsDocumentLoading()) {
+ // It's possible that we get a DOMMetaAdded event after the page
+ // has already been painted, but before the document finishes loading.
+ // In such a case, we've already run SetInitialViewport() on
+ // "before-first-paint", and won't run it again on "load" (because
+ // mPainted=true). But that SetInitialViewport() call didn't know the
+ // "initial-scale" from this meta viewport tag. To ensure we respect
+ // the "initial-scale", call SetInitialViewport() again.
+ // Note: It's important that we only do this if mPainted=true. In the
+ // usual case, we get the DOMMetaAdded before the first paint, sometimes
+ // even before we have a frame tree, and calling SetInitialViewport()
+ // before we have a frame tree will skip some important steps (e.g.
+ // updating display port margins).
+ SetInitialViewport();
+ } else {
+ RefreshViewportSize(mPainted);
+ }
+}
+
+NS_IMETHODIMP
+MobileViewportManager::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!mContext) {
+ return NS_OK;
+ }
+
+ if (mContext->SubjectMatchesDocument(aSubject) &&
+ BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
+ MVM_LOG("%p: got a before-first-paint event\n", this);
+ if (!mPainted) {
+ // before-first-paint message arrived before load event
+ SetInitialViewport();
+ }
+ }
+ return NS_OK;
+}
+
+void MobileViewportManager::SetInitialViewport() {
+ MVM_LOG("%p: setting initial viewport\n", this);
+ mIsFirstPaint = true;
+ mPainted = true;
+ RefreshViewportSize(false);
+}
+
+CSSToScreenScale MobileViewportManager::ClampZoom(
+ const CSSToScreenScale& aZoom, const nsViewportInfo& aViewportInfo) const {
+ CSSToScreenScale zoom = aZoom;
+ if (std::isnan(zoom.scale)) {
+ NS_ERROR("Don't pass NaN to ClampZoom; check caller for 0/0 division");
+ zoom = CSSToScreenScale(1.0);
+ }
+
+ if (zoom < aViewportInfo.GetMinZoom()) {
+ zoom = aViewportInfo.GetMinZoom();
+ MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
+ }
+ if (zoom > aViewportInfo.GetMaxZoom()) {
+ zoom = aViewportInfo.GetMaxZoom();
+ MVM_LOG("%p: Clamped to %f\n", this, zoom.scale);
+ }
+
+ // Non-positive zoom factors can produce NaN or negative viewport sizes,
+ // so we better be sure we've got a positive zoom factor. Just for good
+ // measure, we check our min/max as well as the final clamped value.
+ MOZ_ASSERT(aViewportInfo.GetMinZoom() > CSSToScreenScale(0.0f),
+ "zoom factor must be positive");
+ MOZ_ASSERT(aViewportInfo.GetMaxZoom() > CSSToScreenScale(0.0f),
+ "zoom factor must be positive");
+ MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+ return zoom;
+}
+
+CSSToScreenScale MobileViewportManager::ScaleZoomWithDisplayWidth(
+ const CSSToScreenScale& aZoom, const float& aDisplayWidthChangeRatio,
+ const CSSSize& aNewViewport, const CSSSize& aOldViewport) {
+ float inverseCssWidthChangeRatio =
+ (aNewViewport.width == 0) ? 1.0f
+ : aOldViewport.width / aNewViewport.width;
+ CSSToScreenScale newZoom(aZoom.scale * aDisplayWidthChangeRatio *
+ inverseCssWidthChangeRatio);
+ MVM_LOG("%p: Old zoom was %f, changed by %f * %f to %f\n", this, aZoom.scale,
+ aDisplayWidthChangeRatio, inverseCssWidthChangeRatio, newZoom.scale);
+ return newZoom;
+}
+
+CSSToScreenScale MobileViewportManager::ResolutionToZoom(
+ const LayoutDeviceToLayerScale& aResolution) const {
+ return ViewTargetAs<ScreenPixel>(
+ mContext->CSSToDevPixelScale() * aResolution / ParentLayerToLayerScale(1),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+}
+
+LayoutDeviceToLayerScale MobileViewportManager::ZoomToResolution(
+ const CSSToScreenScale& aZoom) const {
+ return ViewTargetAs<ParentLayerPixel>(
+ aZoom, PixelCastJustification::ScreenIsParentLayerForRoot) /
+ mContext->CSSToDevPixelScale() * ParentLayerToLayerScale(1);
+}
+
+void MobileViewportManager::UpdateResolutionForFirstPaint(
+ const CSSSize& aViewportSize) {
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize);
+ ScreenIntSize compositionSize = GetCompositionSize(displaySize);
+
+ if (mRestoreResolution) {
+ LayoutDeviceToLayerScale restoreResolution(*mRestoreResolution);
+ CSSToScreenScale restoreZoom = ResolutionToZoom(restoreResolution);
+ if (mRestoreDisplaySize) {
+ CSSSize prevViewport =
+ mContext->GetViewportInfo(*mRestoreDisplaySize).GetSize();
+ float restoreDisplayWidthChangeRatio =
+ (mRestoreDisplaySize->width > 0)
+ ? (float)compositionSize.width / (float)mRestoreDisplaySize->width
+ : 1.0f;
+
+ restoreZoom =
+ ScaleZoomWithDisplayWidth(restoreZoom, restoreDisplayWidthChangeRatio,
+ aViewportSize, prevViewport);
+ }
+ MVM_LOG("%p: restored zoom is %f\n", this, restoreZoom.scale);
+ restoreZoom = ClampZoom(restoreZoom, viewportInfo);
+
+ ApplyNewZoom(displaySize, restoreZoom);
+ return;
+ }
+
+ CSSToScreenScale defaultZoom = viewportInfo.GetDefaultZoom();
+ MVM_LOG("%p: default zoom from viewport is %f\n", this, defaultZoom.scale);
+ if (!viewportInfo.IsDefaultZoomValid()) {
+ CSSSize contentSize = aViewportSize;
+ if (Maybe<CSSRect> scrollableRect =
+ mContext->CalculateScrollableRectForRSF()) {
+ contentSize = scrollableRect->Size();
+ }
+ defaultZoom =
+ ComputeIntrinsicScale(viewportInfo, compositionSize, contentSize);
+ }
+ MOZ_ASSERT(viewportInfo.GetMinZoom() <= defaultZoom &&
+ defaultZoom <= viewportInfo.GetMaxZoom());
+
+ ApplyNewZoom(displaySize, defaultZoom);
+}
+
+void MobileViewportManager::UpdateResolutionForViewportSizeChange(
+ const CSSSize& aViewportSize,
+ const Maybe<float>& aDisplayWidthChangeRatio) {
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize);
+
+ CSSToScreenScale zoom = GetZoom();
+ // Non-positive zoom factors can produce NaN or negative viewport sizes,
+ // so we better be sure we've got a positive zoom factor.
+ MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+
+ MOZ_ASSERT(!mIsFirstPaint);
+
+ // If this is not a first paint, then in some cases we want to update the
+ // pre- existing resolution so as to maintain how much actual content is
+ // visible within the display width. Note that "actual content" may be
+ // different with respect to CSS pixels because of the CSS viewport size
+ // changing.
+ //
+ // aDisplayWidthChangeRatio is non-empty if:
+ // (a) The meta-viewport tag information changes, and so the CSS viewport
+ // might change as a result. If this happens after the content has
+ // been painted, we want to adjust the zoom to compensate. OR
+ // (b) The display size changed from a nonzero value to another
+ // nonzero value. This covers the case where e.g. the device was
+ // rotated, and again we want to adjust the zoom to compensate.
+ // Note in particular that aDisplayWidthChangeRatio will be None if all
+ // that happened was a change in the full-zoom. In this case, we still
+ // want to compute a new CSS and visual viewport, but we don't want to update
+ // the resolution.
+ //
+ // Given the above, the algorithm below accounts for all types of changes
+ // I can conceive of:
+ // 1. screen size changes, CSS viewport does not (pages with no meta
+ // viewport or a fixed size viewport)
+ // 2. screen size changes, CSS viewport also does (pages with a
+ // device-width viewport)
+ // 3. screen size remains constant, but CSS viewport changes (meta
+ // viewport tag is added or removed)
+ // 4. neither screen size nor CSS viewport changes
+
+ if (!aDisplayWidthChangeRatio) {
+ UpdateVisualViewportSize(displaySize, zoom);
+ return;
+ }
+
+ // One more complication is that our current zoom level may be the
+ // result of clamping to either the minimum or maximum zoom level
+ // allowed by the viewport. If we naively scale the zoom level with
+ // the change in the display width, we might be scaling one of these
+ // previously clamped values. What we really want to do is to make
+ // scaling of the zoom aware of these minimum and maximum clamping
+ // points for the existing content size, so that we keep display
+ // width changes completely reversible.
+
+ // We don't consider here if we are scaling to a zoom value outside
+ // of our viewport limits, because we'll clamp to the viewport limits
+ // as a final step.
+
+ // Because of the behavior of ShrinkToDisplaySizeIfNeeded, we are
+ // choosing zoom clamping points based on the content size of the
+ // scrollable rect, which might different from aViewportSize.
+ CSSSize contentSize = aViewportSize;
+ if (Maybe<CSSRect> scrollableRect =
+ mContext->CalculateScrollableRectForRSF()) {
+ contentSize = scrollableRect->Size();
+ }
+
+ // We scale the sizes, though we only care about the scaled widths.
+ ScreenSize minZoomDisplaySize = contentSize * viewportInfo.GetMinZoom();
+ ScreenSize maxZoomDisplaySize = contentSize * viewportInfo.GetMaxZoom();
+
+ ScreenSize newDisplaySize(displaySize);
+ ScreenSize oldDisplaySize = newDisplaySize / *aDisplayWidthChangeRatio;
+
+ // To calculate an adjusted ratio, we use some combination of these
+ // four values:
+ float a(minZoomDisplaySize.width);
+ float b(maxZoomDisplaySize.width);
+ float c(oldDisplaySize.width);
+ float d(newDisplaySize.width);
+
+ // The oldDisplaySize value is in one of three "zones":
+ // 1) Less than or equal to minZoomDisplaySize.
+ // 2) Between minZoomDisplaySize and maxZoomDisplaySize.
+ // 3) Greater than or equal to maxZoomDisplaySize.
+
+ // Depending on which zone each are in, the adjusted ratio is shown in
+ // the table below (using the a-b-c-d coding from above):
+
+ // c +---+
+ // | d |
+ // 1 | a |
+ // +---+
+ // | d |
+ // 2 | c |
+ // +---+
+ // | d |
+ // 3 | b |
+ // +---+
+
+ // Conveniently, the denominator is c clamped to a..b.
+ float denominator = clamped(c, a, b);
+
+ float adjustedRatio = d / denominator;
+ CSSToScreenScale adjustedZoom = ScaleZoomWithDisplayWidth(
+ zoom, adjustedRatio, aViewportSize, mMobileViewportSize);
+ CSSToScreenScale newZoom = ClampZoom(adjustedZoom, viewportInfo);
+
+ ApplyNewZoom(displaySize, newZoom);
+}
+
+void MobileViewportManager::UpdateResolutionForContentSizeChange(
+ const CSSSize& aContentSize) {
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize);
+
+ CSSToScreenScale zoom = GetZoom();
+ // Non-positive zoom factors can produce NaN or negative viewport sizes,
+ // so we better be sure we've got a positive zoom factor.
+ MOZ_ASSERT(zoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+
+ ScreenIntSize compositionSize = GetCompositionSize(displaySize);
+ CSSToScreenScale intrinsicScale =
+ ComputeIntrinsicScale(viewportInfo, compositionSize, aContentSize);
+
+ // We try to scale down the contents only IF the document has no
+ // initial-scale AND IF it's not restored documents AND IF the resolution
+ // has never been changed by APZ.
+ if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) {
+ MVM_LOG("%p: conditions preventing shrink-to-fit: %d %d %d\n", this,
+ mRestoreResolution.isSome(), mContext->IsResolutionUpdatedByApz(),
+ viewportInfo.IsDefaultZoomValid());
+ }
+ if (!mRestoreResolution && !mContext->IsResolutionUpdatedByApz() &&
+ !viewportInfo.IsDefaultZoomValid()) {
+ if (zoom != intrinsicScale) {
+ ApplyNewZoom(displaySize, intrinsicScale);
+ }
+ return;
+ }
+
+ // Even in other scenarios, we want to ensure that zoom level is
+ // not _smaller_ than the intrinsic scale, otherwise we might be
+ // trying to show regions where there is no content to show.
+ CSSToScreenScale clampedZoom = zoom;
+
+ if (clampedZoom < intrinsicScale) {
+ clampedZoom = intrinsicScale;
+ }
+
+ // Also clamp to the restrictions imposed by viewportInfo.
+ clampedZoom = ClampZoom(clampedZoom, viewportInfo);
+
+ if (clampedZoom != zoom) {
+ ApplyNewZoom(displaySize, clampedZoom);
+ }
+}
+
+void MobileViewportManager::ApplyNewZoom(const ScreenIntSize& aDisplaySize,
+ const CSSToScreenScale& aNewZoom) {
+ // If the zoom has changed, update the pres shell resolution accordingly.
+ // We characterize this as MainThreadAdjustment, because we don't want our
+ // change here to be remembered as a restore resolution.
+
+ // Non-positive zoom factors can produce NaN or negative viewport sizes,
+ // so we better be sure we've got a positive zoom factor.
+ MOZ_ASSERT(aNewZoom > CSSToScreenScale(0.0f), "zoom factor must be positive");
+
+ LayoutDeviceToLayerScale resolution = ZoomToResolution(aNewZoom);
+ MVM_LOG("%p: setting resolution %f\n", this, resolution.scale);
+ mContext->SetResolutionAndScaleTo(
+ resolution.scale, ResolutionChangeOrigin::MainThreadAdjustment);
+
+ MVM_LOG("%p: New zoom is %f\n", this, aNewZoom.scale);
+
+ UpdateVisualViewportSize(aDisplaySize, aNewZoom);
+}
+
+ScreenIntSize MobileViewportManager::GetCompositionSize(
+ const ScreenIntSize& aDisplaySize) const {
+ if (!mContext) {
+ return ScreenIntSize();
+ }
+
+ // FIXME: Bug 1586986 - To update VisualViewport in response to the dynamic
+ // toolbar transition we probably need to include the dynamic toolbar
+ // _current_ height.
+ ScreenIntSize compositionSize(aDisplaySize);
+ ScreenMargin scrollbars =
+ mContext->ScrollbarAreaToExcludeFromCompositionBounds()
+ // Scrollbars are not subject to resolution scaling, so LD pixels =
+ // Screen pixels for them.
+ * LayoutDeviceToScreenScale(1.0f);
+
+ compositionSize.width =
+ std::max(0.0f, compositionSize.width - scrollbars.LeftRight());
+ compositionSize.height =
+ std::max(0.0f, compositionSize.height - scrollbars.TopBottom());
+
+ return compositionSize;
+}
+
+void MobileViewportManager::UpdateVisualViewportSize(
+ const ScreenIntSize& aDisplaySize, const CSSToScreenScale& aZoom) {
+ if (!mContext) {
+ return;
+ }
+
+ ScreenSize compositionSize = ScreenSize(GetCompositionSize(aDisplaySize));
+
+ CSSSize compSize = compositionSize / aZoom;
+ MVM_LOG("%p: Setting VVPS %s\n", this, ToString(compSize).c_str());
+ mContext->SetVisualViewportSize(compSize);
+
+ UpdateVisualViewportSizeByDynamicToolbar(mContext->GetDynamicToolbarOffset());
+}
+
+CSSToScreenScale MobileViewportManager::GetZoom() const {
+ LayoutDeviceToLayerScale res(mContext->GetResolution());
+ return ResolutionToZoom(res);
+}
+
+void MobileViewportManager::UpdateVisualViewportSizeByDynamicToolbar(
+ ScreenIntCoord aToolbarHeight) {
+ if (!mContext) {
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ displaySize.height += aToolbarHeight;
+ nsSize compSize = CSSSize::ToAppUnits(
+ ScreenSize(GetCompositionSize(displaySize)) / GetZoom());
+
+ if (mVisualViewportSizeUpdatedByDynamicToolbar == compSize) {
+ return;
+ }
+
+ mVisualViewportSizeUpdatedByDynamicToolbar = compSize;
+
+ mContext->PostVisualViewportResizeEventByDynamicToolbar();
+}
+
+void MobileViewportManager::
+ UpdateVisualViewportSizeForPotentialScrollbarChange() {
+ RefreshVisualViewportSize();
+}
+
+void MobileViewportManager::UpdateDisplayPortMargins() {
+ if (!mContext) {
+ return;
+ }
+ mContext->UpdateDisplayPortMargins();
+}
+
+void MobileViewportManager::RefreshVisualViewportSize() {
+ // This function is a subset of RefreshViewportSize, and only updates the
+ // visual viewport size.
+
+ if (!mContext) {
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+
+ if (displaySize.width == 0 || displaySize.height == 0) {
+ return;
+ }
+
+ UpdateVisualViewportSize(displaySize, GetZoom());
+}
+
+void MobileViewportManager::UpdateSizesBeforeReflow() {
+ if (Maybe<LayoutDeviceIntSize> newDisplaySize =
+ mContext->GetDocumentViewerSize()) {
+ mDisplaySize = *newDisplaySize;
+ MVM_LOG("%p: Reflow starting, display size updated to %s\n", this,
+ ToString(mDisplaySize).c_str());
+
+ if (mDisplaySize.width == 0 || mDisplaySize.height == 0) {
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize);
+ mMobileViewportSize = viewportInfo.GetSize();
+ MVM_LOG("%p: MVSize updated to %s\n", this,
+ ToString(mMobileViewportSize).c_str());
+ }
+}
+
+void MobileViewportManager::RefreshViewportSize(bool aForceAdjustResolution) {
+ // This function gets called by the various triggers that may result in a
+ // change of the CSS viewport. In some of these cases (e.g. the meta-viewport
+ // tag changes) we want to update the resolution and in others (e.g. the full
+ // zoom changing) we don't want to update the resolution. See the comment in
+ // UpdateResolutionForViewportSizeChange for some more detail on this.
+ // An important assumption we
+ // make here is that this RefreshViewportSize function will be called
+ // separately for each trigger that changes. For instance it should never get
+ // called such that both the full zoom and the meta-viewport tag have changed;
+ // instead it would get called twice - once after each trigger changes. This
+ // assumption is what allows the aForceAdjustResolution parameter to work as
+ // intended; if this assumption is violated then we will need to add extra
+ // complicated logic in UpdateResolutionForViewportSizeChange to ensure we
+ // only do the resolution update in the right scenarios.
+
+ if (!mContext) {
+ return;
+ }
+
+ Maybe<float> displayWidthChangeRatio;
+ if (Maybe<LayoutDeviceIntSize> newDisplaySize =
+ mContext->GetDocumentViewerSize()) {
+ // See the comment in UpdateResolutionForViewportSizeChange for why we're
+ // doing this.
+ if (mDisplaySize.width > 0) {
+ if (aForceAdjustResolution ||
+ mDisplaySize.width != newDisplaySize->width) {
+ displayWidthChangeRatio =
+ Some((float)newDisplaySize->width / (float)mDisplaySize.width);
+ }
+ } else if (aForceAdjustResolution) {
+ displayWidthChangeRatio = Some(1.0f);
+ }
+
+ MVM_LOG("%p: Display width change ratio is %f\n", this,
+ displayWidthChangeRatio.valueOr(0.0f));
+ mDisplaySize = *newDisplaySize;
+ }
+
+ MVM_LOG("%p: Computing CSS viewport using %d,%d\n", this, mDisplaySize.width,
+ mDisplaySize.height);
+ if (mDisplaySize.width == 0 || mDisplaySize.height == 0) {
+ // We can't do anything useful here, we should just bail out
+ return;
+ }
+
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo viewportInfo = mContext->GetViewportInfo(displaySize);
+ MVM_LOG("%p: viewport info has zooms min=%f max=%f default=%f,valid=%d\n",
+ this, viewportInfo.GetMinZoom().scale,
+ viewportInfo.GetMaxZoom().scale, viewportInfo.GetDefaultZoom().scale,
+ viewportInfo.IsDefaultZoomValid());
+
+ CSSSize viewport = viewportInfo.GetSize();
+ MVM_LOG("%p: Computed CSS viewport %s\n", this, ToString(viewport).c_str());
+
+ if (!mIsFirstPaint && mMobileViewportSize == viewport) {
+ // Nothing changed, so no need to do a reflow
+ return;
+ }
+
+ // If it's the first-paint or the viewport changed, we need to update
+ // various APZ properties (the zoom and some things that might depend on it)
+ MVM_LOG("%p: Updating properties because %d || %d\n", this, mIsFirstPaint,
+ mMobileViewportSize != viewport);
+
+ if (mManagerType == ManagerType::VisualAndMetaViewport &&
+ (aForceAdjustResolution || mContext->AllowZoomingForDocument())) {
+ MVM_LOG("%p: Updating resolution because %d || %d\n", this,
+ aForceAdjustResolution, mContext->AllowZoomingForDocument());
+ if (mIsFirstPaint) {
+ UpdateResolutionForFirstPaint(viewport);
+ } else {
+ UpdateResolutionForViewportSizeChange(viewport, displayWidthChangeRatio);
+ }
+ } else {
+ // Even without zoom, we need to update that the visual viewport size
+ // has changed.
+ MVM_LOG("%p: Updating VV size\n", this);
+ RefreshVisualViewportSize();
+ }
+ if (gfxPlatform::AsyncPanZoomEnabled()) {
+ UpdateDisplayPortMargins();
+ }
+
+ // Update internal state.
+ mMobileViewportSize = viewport;
+
+ if (mManagerType == ManagerType::VisualViewportOnly) {
+ MVM_LOG("%p: Visual-only, so aborting before reflow\n", this);
+ mIsFirstPaint = false;
+ return;
+ }
+
+ RefPtr<MobileViewportManager> strongThis(this);
+
+ // Kick off a reflow.
+ MVM_LOG("%p: Triggering reflow with viewport %s\n", this,
+ ToString(viewport).c_str());
+ mContext->Reflow(viewport);
+
+ // We are going to fit the content to the display width if the initial-scale
+ // is not specied and if the content is still wider than the display width.
+ ShrinkToDisplaySizeIfNeeded();
+
+ mIsFirstPaint = false;
+}
+
+void MobileViewportManager::ShrinkToDisplaySizeIfNeeded() {
+ if (!mContext) {
+ return;
+ }
+
+ if (mManagerType == ManagerType::VisualViewportOnly) {
+ MVM_LOG("%p: Visual-only, so aborting ShrinkToDisplaySizeIfNeeded\n", this);
+ return;
+ }
+
+ if (!mContext->AllowZoomingForDocument() || mContext->IsInReaderMode()) {
+ // If zoom is disabled, we don't scale down wider contents to fit them
+ // into device screen because users won't be able to zoom out the tiny
+ // contents.
+ // We special-case reader mode, because it doesn't allow zooming, but
+ // the restriction is often not yet in place at the time this logic
+ // runs.
+ return;
+ }
+
+ if (Maybe<CSSRect> scrollableRect =
+ mContext->CalculateScrollableRectForRSF()) {
+ MVM_LOG("%p: ShrinkToDisplaySize using scrollableRect %s\n", this,
+ ToString(scrollableRect->Size()).c_str());
+ UpdateResolutionForContentSizeChange(scrollableRect->Size());
+ }
+}
+
+CSSSize MobileViewportManager::GetIntrinsicCompositionSize() const {
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ ScreenIntSize compositionSize = GetCompositionSize(displaySize);
+ CSSToScreenScale intrinsicScale =
+ ComputeIntrinsicScale(mContext->GetViewportInfo(displaySize),
+ compositionSize, mMobileViewportSize);
+
+ return ScreenSize(compositionSize) / intrinsicScale;
+}
+
+ParentLayerSize MobileViewportManager::GetCompositionSizeWithoutDynamicToolbar()
+ const {
+ ScreenIntSize displaySize = ViewAs<ScreenPixel>(
+ mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ return ViewAs<ParentLayerPixel>(
+ ScreenSize(GetCompositionSize(displaySize)),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+}
diff --git a/layout/base/MobileViewportManager.h b/layout/base/MobileViewportManager.h
new file mode 100644
index 0000000000..1d4fba1d54
--- /dev/null
+++ b/layout/base/MobileViewportManager.h
@@ -0,0 +1,220 @@
+/* -*- 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 MobileViewportManager_h_
+#define MobileViewportManager_h_
+
+#include "mozilla/Logging.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MVMContext.h"
+#include "mozilla/PresShellForwards.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+#include "Units.h"
+
+class nsViewportInfo;
+
+namespace mozilla {
+class MVMContext;
+namespace dom {
+class Document;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class MobileViewportManager final : public nsIDOMEventListener,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIOBSERVER
+
+ /* The MobileViewportManager might be required to handle meta-viewport tags
+ * and changes, or it might not (e.g. if we are in a desktop-zooming setup).
+ * This enum indicates which mode the manager is in. It might make sense to
+ * split these two "modes" into two separate classes but for now they have a
+ * bunch of shared code and it's uncertain if that shared set will expand or
+ * contract. */
+ enum class ManagerType { VisualAndMetaViewport, VisualViewportOnly };
+
+ explicit MobileViewportManager(mozilla::MVMContext* aContext,
+ ManagerType aType);
+ void Destroy();
+
+ ManagerType GetManagerType() { return mManagerType; }
+
+ /* Provide a resolution to use during the first paint instead of the default
+ * resolution computed from the viewport info metadata. This is in the same
+ * "units" as the argument to nsDOMWindowUtils::SetResolutionAndScaleTo.
+ * Also takes the previous display dimensions as they were at the time the
+ * resolution was stored in order to correctly adjust the resolution if the
+ * device was rotated in the meantime. */
+ void SetRestoreResolution(float aResolution,
+ mozilla::LayoutDeviceIntSize aDisplaySize);
+
+ /* Compute the "intrinsic resolution", which is the smallest resolution at
+ * which the layout viewport fills the visual viewport. (In typical
+ * scenarios, where the aspect ratios of the two viewports match, it's the
+ * resolution at which they are the same size.)
+ *
+ * The returned resolution is suitable for passing to
+ * PresShell::SetResolutionAndScaleTo(). It's not in typed units for
+ * reasons explained at the declaration of FrameMetrics::mPresShellResolution.
+ */
+ float ComputeIntrinsicResolution() const;
+
+ /* The only direct calls to this should be in test code.
+ * Normally, it gets called by HandleEvent().
+ */
+ void HandleDOMMetaAdded();
+
+ private:
+ void SetRestoreResolution(float aResolution);
+
+ public:
+ /* Notify the MobileViewportManager that a reflow is about to happen. This
+ * triggers the MVM to update its internal notion of display size and CSS
+ * viewport, so that code that queries those during the reflow gets an
+ * up-to-date value.
+ */
+ void UpdateSizesBeforeReflow();
+
+ /* Notify the MobileViewportManager that a reflow was requested in the
+ * presShell.*/
+ void RequestReflow(bool aForceAdjustResolution);
+
+ /* Notify the MobileViewportManager that the resolution on the presShell was
+ * updated, and the visual viewport size needs to be updated. */
+ void ResolutionUpdated(mozilla::ResolutionChangeOrigin aOrigin);
+
+ /* Called to compute the initial viewport on page load or before-first-paint,
+ * whichever happens first. Also called directly if we are created after the
+ * presShell is initialized. */
+ void SetInitialViewport();
+
+ const mozilla::LayoutDeviceIntSize& DisplaySize() const {
+ return mDisplaySize;
+ };
+
+ /*
+ * Shrink the content to fit it to the display width if no initial-scale is
+ * specified and if the content is still wider than the display width.
+ */
+ void ShrinkToDisplaySizeIfNeeded();
+
+ /*
+ * Similar to UpdateVisualViewportSize but this should be called only when we
+ * need to update visual viewport size in response to dynamic toolbar
+ * transitions.
+ * This function doesn't cause any reflows, just fires a visual viewport
+ * resize event.
+ */
+ void UpdateVisualViewportSizeByDynamicToolbar(
+ mozilla::ScreenIntCoord aToolbarHeight);
+
+ nsSize GetVisualViewportSizeUpdatedByDynamicToolbar() const {
+ return mVisualViewportSizeUpdatedByDynamicToolbar;
+ }
+
+ /*
+ * This refreshes the visual viewport size based on the most recently
+ * available information. It is intended to be called in particular after
+ * the root scrollframe does a reflow, which may make scrollbars appear or
+ * disappear if the content changed size.
+ */
+ void UpdateVisualViewportSizeForPotentialScrollbarChange();
+
+ /*
+ * Returns the composition size in CSS units when zoomed to the intrinsic
+ * scale.
+ */
+ mozilla::CSSSize GetIntrinsicCompositionSize() const;
+
+ mozilla::ParentLayerSize GetCompositionSizeWithoutDynamicToolbar() const;
+
+ static mozilla::LazyLogModule gLog;
+
+ private:
+ ~MobileViewportManager();
+
+ /* Main helper method to update the CSS viewport and any other properties that
+ * need updating. */
+ void RefreshViewportSize(bool aForceAdjustResolution);
+
+ /* Secondary main helper method to update just the visual viewport size. */
+ void RefreshVisualViewportSize();
+
+ /* Helper to clamp the given zoom by the min/max in the viewport info. */
+ mozilla::CSSToScreenScale ClampZoom(
+ const mozilla::CSSToScreenScale& aZoom,
+ const nsViewportInfo& aViewportInfo) const;
+
+ /* Helper to update the given zoom according to changed display and viewport
+ * widths. */
+ mozilla::CSSToScreenScale ScaleZoomWithDisplayWidth(
+ const mozilla::CSSToScreenScale& aZoom,
+ const float& aDisplayWidthChangeRatio,
+ const mozilla::CSSSize& aNewViewport,
+ const mozilla::CSSSize& aOldViewport);
+
+ mozilla::CSSToScreenScale ResolutionToZoom(
+ const mozilla::LayoutDeviceToLayerScale& aResolution) const;
+ mozilla::LayoutDeviceToLayerScale ZoomToResolution(
+ const mozilla::CSSToScreenScale& aZoom) const;
+
+ /* Updates the presShell resolution and the visual viewport size for various
+ * types of changes. */
+ void UpdateResolutionForFirstPaint(const mozilla::CSSSize& aViewportSize);
+ void UpdateResolutionForViewportSizeChange(
+ const mozilla::CSSSize& aViewportSize,
+ const mozilla::Maybe<float>& aDisplayWidthChangeRatio);
+ void UpdateResolutionForContentSizeChange(
+ const mozilla::CSSSize& aContentSize);
+
+ void ApplyNewZoom(const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSToScreenScale& aNewZoom);
+
+ void UpdateVisualViewportSize(const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSToScreenScale& aZoom);
+
+ /* Updates the displayport margins for the presShell's root scrollable frame
+ */
+ void UpdateDisplayPortMargins();
+
+ /* Helper function for ComputeIntrinsicResolution(). */
+ mozilla::CSSToScreenScale ComputeIntrinsicScale(
+ const nsViewportInfo& aViewportInfo,
+ const mozilla::ScreenIntSize& aDisplaySize,
+ const mozilla::CSSSize& aViewportOrContentSize) const;
+
+ /*
+ * Returns the screen size subtracted the scrollbar sizes from |aDisplaySize|.
+ */
+ mozilla::ScreenIntSize GetCompositionSize(
+ const mozilla::ScreenIntSize& aDisplaySize) const;
+
+ mozilla::CSSToScreenScale GetZoom() const;
+
+ RefPtr<mozilla::MVMContext> mContext;
+ ManagerType mManagerType;
+ bool mIsFirstPaint;
+ bool mPainted;
+ mozilla::LayoutDeviceIntSize mDisplaySize;
+ mozilla::CSSSize mMobileViewportSize;
+ mozilla::Maybe<float> mRestoreResolution;
+ mozilla::Maybe<mozilla::ScreenIntSize> mRestoreDisplaySize;
+ /*
+ * The visual viewport size updated by the dynamic toolbar transitions. This
+ * is typically used for the VisualViewport width/height APIs.
+ * NOTE: If you want to use this value, you should make sure to flush
+ * position:fixed elements layout and update
+ * FrameMetrics.mFixedLayerMargins to conform with this value.
+ */
+ nsSize mVisualViewportSizeUpdatedByDynamicToolbar;
+};
+
+#endif
diff --git a/layout/base/MotionPathUtils.cpp b/layout/base/MotionPathUtils.cpp
new file mode 100644
index 0000000000..c81020645d
--- /dev/null
+++ b/layout/base/MotionPathUtils.cpp
@@ -0,0 +1,762 @@
+/* -*- 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 "mozilla/MotionPathUtils.h"
+
+#include "gfxPlatform.h"
+#include "mozilla/dom/SVGGeometryElement.h"
+#include "mozilla/dom/SVGPathData.h"
+#include "mozilla/dom/SVGViewportElement.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/layers/LayersMessages.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/ShapeUtils.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsStyleTransformMatrix.h"
+
+#include <math.h>
+
+namespace mozilla {
+
+using nsStyleTransformMatrix::TransformReferenceBox;
+
+/* static */
+CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
+ if (!aFrame.HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ return {};
+ }
+
+ auto transformBox = aFrame.StyleDisplay()->mTransformBox;
+ if (transformBox == StyleTransformBox::ViewBox ||
+ transformBox == StyleTransformBox::BorderBox) {
+ return {};
+ }
+
+ if (aFrame.IsSVGContainerFrame()) {
+ nsRect boxRect = nsLayoutUtils::ComputeSVGReferenceRect(
+ const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
+ return CSSPoint::FromAppUnits(boxRect.TopLeft());
+ }
+ return CSSPoint::FromAppUnits(aFrame.GetPosition());
+}
+
+// Convert the StyleCoordBox into the StyleGeometryBox in CSS layout.
+// https://drafts.csswg.org/css-box-4/#keywords
+static StyleGeometryBox CoordBoxToGeometryBoxInCSSLayout(
+ StyleCoordBox aCoordBox) {
+ switch (aCoordBox) {
+ case StyleCoordBox::ContentBox:
+ return StyleGeometryBox::ContentBox;
+ case StyleCoordBox::PaddingBox:
+ return StyleGeometryBox::PaddingBox;
+ case StyleCoordBox::BorderBox:
+ return StyleGeometryBox::BorderBox;
+ case StyleCoordBox::FillBox:
+ return StyleGeometryBox::ContentBox;
+ case StyleCoordBox::StrokeBox:
+ case StyleCoordBox::ViewBox:
+ return StyleGeometryBox::BorderBox;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
+ return StyleGeometryBox::BorderBox;
+}
+
+/* static */
+const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
+ const nsIFrame* aFrame, nsRect& aOutputRect) {
+ const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
+ if (offsetPath.IsNone()) {
+ return nullptr;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
+ auto* viewportElement =
+ dom::SVGElement::FromNode(aFrame->GetContent())->GetCtx();
+ aOutputRect = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
+ return viewportElement ? viewportElement->GetPrimaryFrame() : nullptr;
+ }
+
+ const nsIFrame* containingBlock = aFrame->GetContainingBlock();
+ const StyleCoordBox coordBox = offsetPath.IsCoordBox()
+ ? offsetPath.AsCoordBox()
+ : offsetPath.AsOffsetPath().coord_box;
+ aOutputRect = nsLayoutUtils::ComputeHTMLReferenceRect(
+ containingBlock, CoordBoxToGeometryBoxInCSSLayout(coordBox));
+ return containingBlock;
+}
+
+/* static */
+CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
+ // We use the border-box size to calculate the reduced path length when using
+ // "contain" keyword.
+ // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
+ //
+ // Note: Per the spec, border-box is treated as stroke-box in the SVG context,
+ // https://drafts.csswg.org/css-box-4/#valdef-box-border-box
+
+ // To calculate stroke bounds for an element with `non-scaling-stroke` we
+ // need to resolve its transform to its outer-svg, but to resolve that
+ // transform when it has `transform-box:stroke-box` (or `border-box`)
+ // may require its stroke bounds. There's no ideal way to break this
+ // cyclical dependency, but we break it by using the FillBox.
+ // https://github.com/w3c/csswg-drafts/issues/9640
+
+ const auto size = CSSSize::FromAppUnits(
+ (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
+ ? nsLayoutUtils::ComputeSVGReferenceRect(
+ aFrame, aFrame->StyleSVGReset()->HasNonScalingStroke()
+ ? StyleGeometryBox::FillBox
+ : StyleGeometryBox::StrokeBox)
+ : nsLayoutUtils::ComputeHTMLReferenceRect(
+ aFrame, StyleGeometryBox::BorderBox))
+ .Size());
+ return std::max(size.width, size.height);
+}
+
+/* static */
+nsTArray<nscoord> MotionPathUtils::ComputeBorderRadii(
+ const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) {
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(
+ StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
+ aCoordBox);
+ nsTArray<nscoord> result(8);
+ result.SetLength(8);
+ if (!ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect,
+ result.Elements())) {
+ result.Clear();
+ }
+ return result;
+}
+
+// The distance is measured between the origin and the intersection of the ray
+// with the reference box of the containing block.
+// Note: |aOrigin| and |aContaingBlock| should be in the same coordinate system
+// (i.e. the nsIFrame::mRect of the containing block).
+// https://drafts.fxtf.org/motion-1/#size-sides
+static CSSCoord ComputeSides(const CSSPoint& aOrigin,
+ const CSSRect& aContainingBlock,
+ const StyleAngle& aAngle) {
+ const CSSPoint& topLeft = aContainingBlock.TopLeft();
+ // Given an acute angle |theta| (i.e. |t|) of a right-angled triangle, the
+ // hypotenuse |h| is the side that connects the two acute angles. The side
+ // |b| adjacent to |theta| is the side of the triangle that connects |theta|
+ // to the right angle.
+ //
+ // e.g. if the angle |t| is 0 ~ 90 degrees, and b * tan(theta) <= b',
+ // h = b / cos(t):
+ // b*tan(t)
+ // (topLeft) #--------*-----*--# (aContainingBlock.XMost(), topLeft.y)
+ // | | / |
+ // | | / |
+ // | b h |
+ // | |t/ |
+ // | |/ |
+ // (aOrigin) *---b'---* (aContainingBlock.XMost(), aOrigin.y)
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // | | |
+ // #-----------------# (aContainingBlock.XMost(),
+ // (topLeft.x, aContainingBlock.YMost())
+ // aContainingBlock.YMost())
+ const double theta = aAngle.ToRadians();
+ double sint = std::sin(theta);
+ double cost = std::cos(theta);
+
+ const double b = cost >= 0 ? aOrigin.y.value - topLeft.y
+ : aContainingBlock.YMost() - aOrigin.y.value;
+ const double bPrime = sint >= 0 ? aContainingBlock.XMost() - aOrigin.x.value
+ : aOrigin.x.value - topLeft.x;
+ sint = std::fabs(sint);
+ cost = std::fabs(cost);
+
+ // The trigonometric formula here doesn't work well if |theta| is 0deg or
+ // 90deg, so we handle these edge cases first.
+ if (sint < std::numeric_limits<double>::epsilon()) {
+ // For 0deg (or 180deg), we use |b| directly.
+ return static_cast<float>(b);
+ }
+
+ if (cost < std::numeric_limits<double>::epsilon()) {
+ // For 90deg (or 270deg), we use |bPrime| directly. This can also avoid 0/0
+ // if both |b| and |cost| are 0.0. (i.e. b / cost).
+ return static_cast<float>(bPrime);
+ }
+
+ // Note: The following formula works well only when 0 < theta < 90deg. So we
+ // handle 0deg and 90deg above first.
+ //
+ // If |b * tan(theta)| is larger than |bPrime|, the intersection is
+ // on the other side, and |b'| is the opposite side of angle |theta| in this
+ // case.
+ //
+ // e.g. If b * tan(theta) > b', h = b' / sin(theta):
+ // *----*
+ // | |
+ // | /|
+ // b /t|
+ // |t/ |
+ // |/ |
+ // *-b'-*
+ if (b * sint > bPrime * cost) {
+ return bPrime / sint;
+ }
+ return b / cost;
+}
+
+// Compute the position of "at <position>" together with offset starting
+// position (i.e. offset-position).
+static nsPoint ComputePosition(const StylePositionOrAuto& aAtPosition,
+ const StyleOffsetPosition& aOffsetPosition,
+ const nsRect& aCoordBox,
+ const nsPoint& aCurrentCoord) {
+ if (aAtPosition.IsPosition()) {
+ // Resolve this by using the <position> to position a 0x0 object area within
+ // the box’s containing block.
+ return ShapeUtils::ComputePosition(aAtPosition.AsPosition(), aCoordBox);
+ }
+
+ MOZ_ASSERT(aAtPosition.IsAuto(), "\"at <position>\" should be omitted");
+
+ // Use the offset starting position of the element, given by offset-position.
+ // https://drafts.fxtf.org/motion-1/#valdef-ray-at-position
+ if (aOffsetPosition.IsPosition()) {
+ return ShapeUtils::ComputePosition(aOffsetPosition.AsPosition(), aCoordBox);
+ }
+
+ if (aOffsetPosition.IsNormal()) {
+ // If the element doesn’t have an offset starting position either, it
+ // behaves as at center.
+ const StylePosition& center = StylePosition::FromPercentage(0.5);
+ return ShapeUtils::ComputePosition(center, aCoordBox);
+ }
+
+ MOZ_ASSERT(aOffsetPosition.IsAuto());
+ return aCurrentCoord;
+}
+
+static CSSCoord ComputeRayPathLength(const StyleRaySize aRaySizeType,
+ const StyleAngle& aAngle,
+ const CSSPoint& aOrigin,
+ const CSSRect& aContainingBlock) {
+ if (aRaySizeType == StyleRaySize::Sides) {
+ // If the initial position is not within the box, the distance is 0.
+ //
+ // Note: If the origin is at XMost() (and/or YMost()), we should consider it
+ // to be inside containing block (because we expect 100% x (or y) coordinate
+ // is still to be considered inside the containing block.
+ if (!aContainingBlock.ContainsInclusively(aOrigin)) {
+ return 0.0;
+ }
+
+ return ComputeSides(aOrigin, aContainingBlock, aAngle);
+ }
+
+ // left: the length between the origin and the left side.
+ // right: the length between the origin and the right side.
+ // top: the length between the origin and the top side.
+ // bottom: the lenght between the origin and the bottom side.
+ const CSSPoint& topLeft = aContainingBlock.TopLeft();
+ const CSSCoord left = std::abs(aOrigin.x - topLeft.x);
+ const CSSCoord right = std::abs(aContainingBlock.XMost() - aOrigin.x);
+ const CSSCoord top = std::abs(aOrigin.y - topLeft.y);
+ const CSSCoord bottom = std::abs(aContainingBlock.YMost() - aOrigin.y);
+
+ switch (aRaySizeType) {
+ case StyleRaySize::ClosestSide:
+ return std::min({left, right, top, bottom});
+
+ case StyleRaySize::FarthestSide:
+ return std::max({left, right, top, bottom});
+
+ case StyleRaySize::ClosestCorner:
+ case StyleRaySize::FarthestCorner: {
+ CSSCoord h = 0;
+ CSSCoord v = 0;
+ if (aRaySizeType == StyleRaySize::ClosestCorner) {
+ h = std::min(left, right);
+ v = std::min(top, bottom);
+ } else {
+ h = std::max(left, right);
+ v = std::max(top, bottom);
+ }
+ return sqrt(h.value * h.value + v.value * v.value);
+ }
+ case StyleRaySize::Sides:
+ MOZ_ASSERT_UNREACHABLE("Unsupported ray size");
+ }
+
+ return 0.0;
+}
+
+static CSSCoord ComputeRayUsedDistance(
+ const StyleRayFunction& aRay, const LengthPercentage& aDistance,
+ const CSSCoord& aPathLength, const CSSCoord& aRayContainReferenceLength) {
+ CSSCoord usedDistance = aDistance.ResolveToCSSPixels(aPathLength);
+ if (!aRay.contain) {
+ return usedDistance;
+ }
+
+ // The length of the offset path is reduced so that the element stays within
+ // the containing block even at offset-distance: 100%. Specifically, the
+ // path’s length is reduced by half the width or half the height of the
+ // element’s border box, whichever is larger, and floored at zero.
+ // https://drafts.fxtf.org/motion-1/#valdef-ray-contain
+ return std::max((usedDistance - aRayContainReferenceLength / 2.0f).value,
+ 0.0f);
+}
+
+/* static */
+Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
+ const OffsetPathData& aPath, const LengthPercentage& aDistance,
+ const StyleOffsetRotate& aRotate, const StylePositionOrAuto& aAnchor,
+ const StyleOffsetPosition& aPosition, const CSSPoint& aTransformOrigin,
+ TransformReferenceBox& aRefBox, const CSSPoint& aAnchorPointAdjustment) {
+ if (aPath.IsNone()) {
+ return Nothing();
+ }
+
+ // Compute the point and angle for creating the equivalent translate and
+ // rotate.
+ double directionAngle = 0.0;
+ gfx::Point point;
+ if (aPath.IsShape()) {
+ const auto& data = aPath.AsShape();
+ RefPtr<gfx::Path> path = data.mGfxPath;
+ MOZ_ASSERT(path, "The empty path is not allowed");
+
+ // Per the spec, we have to convert offset distance to pixels, with 100%
+ // being converted to total length. So here |gfxPath| is built with CSS
+ // pixel, and we calculate |pathLength| and |computedDistance| with CSS
+ // pixel as well.
+ gfx::Float pathLength = path->ComputeLength();
+ gfx::Float usedDistance =
+ aDistance.ResolveToCSSPixels(CSSCoord(pathLength));
+ if (data.mIsClosedLoop) {
+ // Per the spec, let used offset distance be equal to offset distance
+ // modulus the total length of the path. If the total length of the path
+ // is 0, used offset distance is also 0.
+ usedDistance = pathLength > 0.0 ? fmod(usedDistance, pathLength) : 0.0;
+ // We make sure |usedDistance| is 0.0 or a positive value.
+ if (usedDistance < 0.0) {
+ usedDistance += pathLength;
+ }
+ } else {
+ // Per the spec, for unclosed interval, let used offset distance be equal
+ // to offset distance clamped by 0 and the total length of the path.
+ usedDistance = clamped(usedDistance, 0.0f, pathLength);
+ }
+ gfx::Point tangent;
+ point = path->ComputePointAtLength(usedDistance, &tangent);
+ // Basically, |point| should be a relative distance between the current
+ // position and the target position. The built |path| is in the coordinate
+ // system of its containing block. Therefore, we have to take the current
+ // position of this box into account to offset the translation so it's final
+ // position is not affected by other boxes in the same containing block.
+ point -= NSPointToPoint(data.mCurrentPosition, AppUnitsPerCSSPixel());
+ directionAngle = atan2((double)tangent.y, (double)tangent.x); // in Radian.
+ } else if (aPath.IsRay()) {
+ const auto& ray = aPath.AsRay();
+ MOZ_ASSERT(ray.mRay);
+
+ // Compute the origin, where the ray’s line begins (the 0% position).
+ // https://drafts.fxtf.org/motion-1/#ray-origin
+ const CSSPoint origin = CSSPoint::FromAppUnits(ComputePosition(
+ ray.mRay->position, aPosition, ray.mCoordBox, ray.mCurrentPosition));
+ const CSSCoord pathLength =
+ ComputeRayPathLength(ray.mRay->size, ray.mRay->angle, origin,
+ CSSRect::FromAppUnits(ray.mCoordBox));
+ const CSSCoord usedDistance = ComputeRayUsedDistance(
+ *ray.mRay, aDistance, pathLength, ray.mContainReferenceLength);
+
+ // 0deg pointing up and positive angles representing clockwise rotation.
+ directionAngle =
+ StyleAngle{ray.mRay->angle.ToDegrees() - 90.0f}.ToRadians();
+
+ // The vector from the current position of this box to the origin of this
+ // polar coordinate system.
+ const gfx::Point vectorToOrigin =
+ (origin - CSSPoint::FromAppUnits(ray.mCurrentPosition))
+ .ToUnknownPoint();
+ // |vectorToOrigin| + The vector from the origin to this polar coordinate,
+ // (|usedDistance|, |directionAngle|), i.e. the vector from the current
+ // position to this polar coordinate.
+ point =
+ vectorToOrigin +
+ gfx::Point(usedDistance * static_cast<gfx::Float>(cos(directionAngle)),
+ usedDistance * static_cast<gfx::Float>(sin(directionAngle)));
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unsupported offset-path value");
+ return Nothing();
+ }
+
+ // If |rotate.auto_| is true, the element should be rotated by the angle of
+ // the direction (i.e. directional tangent vector) of the offset-path, and the
+ // computed value of <angle> is added to this.
+ // Otherwise, the element has a constant clockwise rotation transformation
+ // applied to it by the specified rotation angle. (i.e. Don't need to
+ // consider the direction of the path.)
+ gfx::Float angle = static_cast<gfx::Float>(
+ (aRotate.auto_ ? directionAngle : 0.0) + aRotate.angle.ToRadians());
+
+ // Compute the offset for motion path translate.
+ // Bug 1559232: the translate parameters will be adjusted more after we
+ // support offset-position.
+ // Per the spec, the default offset-anchor is `auto`, so initialize the anchor
+ // point to transform-origin.
+ CSSPoint anchorPoint(aTransformOrigin);
+ gfx::Point shift;
+ if (!aAnchor.IsAuto()) {
+ const auto& pos = aAnchor.AsPosition();
+ anchorPoint = nsStyleTransformMatrix::Convert2DPosition(
+ pos.horizontal, pos.vertical, aRefBox);
+ // We need this value to shift the origin from transform-origin to
+ // offset-anchor (and vice versa).
+ // See nsStyleTransformMatrix::ReadTransform for more details.
+ shift = (anchorPoint - aTransformOrigin).ToUnknownPoint();
+ }
+
+ anchorPoint += aAnchorPointAdjustment;
+
+ return Some(ResolvedMotionPathData{point - anchorPoint.ToUnknownPoint(),
+ angle, shift});
+}
+
+static inline bool IsClosedLoop(const StyleSVGPathData& aPathData) {
+ return !aPathData._0.AsSpan().empty() &&
+ aPathData._0.AsSpan().rbegin()->IsClosePath();
+}
+
+// Create a path for "inset(0 round X)", where X is the value of border-radius
+// on the element that establishes the containing block for this element.
+static already_AddRefed<gfx::Path> BuildSimpleInsetPath(
+ const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox,
+ gfx::PathBuilder* aPathBuilder) {
+ if (!aPathBuilder) {
+ return nullptr;
+ }
+
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(
+ StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
+ aCoordBox);
+ nscoord radii[8];
+ const bool hasRadii =
+ ShapeUtils::ComputeRectRadii(aBorderRadius, aCoordBox, insetRect, radii);
+ return ShapeUtils::BuildRectPath(insetRect, hasRadii ? radii : nullptr,
+ aCoordBox, AppUnitsPerCSSPixel(),
+ aPathBuilder);
+}
+
+// Create a path for `path("m 0 0")`, which is the default URL path if we cannot
+// resolve a SVG shape element.
+// https://drafts.fxtf.org/motion-1/#valdef-offset-path-url
+static already_AddRefed<gfx::Path> BuildDefaultPathForURL(
+ gfx::PathBuilder* aBuilder) {
+ if (!aBuilder) {
+ return nullptr;
+ }
+
+ Array<const StylePathCommand, 1> array(StylePathCommand::MoveTo(
+ StyleCoordPair(gfx::Point{0.0, 0.0}), StyleIsAbsolute::No));
+ return SVGPathData::BuildPath(array, aBuilder, StyleStrokeLinecap::Butt, 0.0);
+}
+
+// Generate data for motion path on the main thread.
+static OffsetPathData GenerateOffsetPathData(const nsIFrame* aFrame) {
+ const StyleOffsetPath& offsetPath = aFrame->StyleDisplay()->mOffsetPath;
+ if (offsetPath.IsNone()) {
+ return OffsetPathData::None();
+ }
+
+ // Handle ray().
+ if (offsetPath.IsRay()) {
+ nsRect coordBox;
+ const nsIFrame* containingBlockFrame =
+ MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
+ return !containingBlockFrame
+ ? OffsetPathData::None()
+ : OffsetPathData::Ray(
+ offsetPath.AsRay(), std::move(coordBox),
+ aFrame->GetOffsetTo(containingBlockFrame),
+ MotionPathUtils::GetRayContainReferenceSize(
+ const_cast<nsIFrame*>(aFrame)));
+ }
+
+ // Handle path(). We cache it so we handle it separately.
+ // FIXME: Bug 1837042, cache gfx::Path for shapes other than path(). Once we
+ // cache all basic shapes, we can merge this branch into other basic shapes.
+ if (offsetPath.IsPath()) {
+ const StyleSVGPathData& pathData = offsetPath.AsSVGPathData();
+ RefPtr<gfx::Path> gfxPath =
+ aFrame->GetProperty(nsIFrame::OffsetPathCache());
+ MOZ_ASSERT(gfxPath || pathData._0.IsEmpty(),
+ "Should have a valid cached gfx::Path or an empty path string");
+ // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
+ // to give it the current box position.
+ return OffsetPathData::Shape(gfxPath.forget(), {}, IsClosedLoop(pathData));
+ }
+
+ nsRect coordBox;
+ const nsIFrame* containingFrame =
+ MotionPathUtils::GetOffsetPathReferenceBox(aFrame, coordBox);
+ if (!containingFrame || coordBox.IsEmpty()) {
+ return OffsetPathData::None();
+ }
+ nsPoint currentPosition = aFrame->GetOffsetTo(containingFrame);
+ RefPtr<gfx::PathBuilder> builder = MotionPathUtils::GetPathBuilder();
+
+ if (offsetPath.IsUrl()) {
+ dom::SVGGeometryElement* element =
+ SVGObserverUtils::GetAndObserveGeometry(const_cast<nsIFrame*>(aFrame));
+ if (!element) {
+ // Note: This behaves as path("m 0 0") (a <basic-shape>).
+ RefPtr<gfx::Path> path = BuildDefaultPathForURL(builder);
+ // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
+ // to give it the current box position.
+ return path ? OffsetPathData::Shape(path.forget(), {}, false)
+ : OffsetPathData::None();
+ }
+
+ // We just need this path to calculate the specific point and direction
+ // angle, so use measuring function and get the benefit of caching the path
+ // in the SVG shape element.
+ RefPtr<gfx::Path> path = element->GetOrBuildPathForMeasuring();
+
+ // The built |path| from SVG shape element doesn't take |coordBox| into
+ // account. It uses the SVG viewport as its coordinate system. So after
+ // mapping it into the CSS layout, we should use |coordBox| as its viewport
+ // and user coordinate system. |currentPosition| is based on the border-box
+ // of the containing block. Therefore, we have to apply an extra translation
+ // to put it at the correct position based on |coordBox|.
+ //
+ // Note: we reuse |OffsetPathData::ShapeData::mCurrentPosition| to include
+ // this extra translation, so we don't have to add an extra field.
+ nsPoint positionInCoordBox = currentPosition - coordBox.TopLeft();
+ return path ? OffsetPathData::Shape(path.forget(),
+ std::move(positionInCoordBox),
+ element->IsClosedLoop())
+ : OffsetPathData::None();
+ }
+
+ // The rest part is to handle "<basic-shape> || <coord-box>".
+ MOZ_ASSERT(offsetPath.IsBasicShapeOrCoordBox());
+
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ RefPtr<gfx::Path> path =
+ disp->mOffsetPath.IsCoordBox()
+ ? BuildSimpleInsetPath(containingFrame->StyleBorder()->mBorderRadius,
+ coordBox, builder)
+ : MotionPathUtils::BuildPath(
+ disp->mOffsetPath.AsOffsetPath().path->AsShape(),
+ disp->mOffsetPosition, coordBox, currentPosition, builder);
+ return path ? OffsetPathData::Shape(path.forget(), std::move(currentPosition),
+ true)
+ : OffsetPathData::None();
+}
+
+/* static*/
+Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
+ const nsIFrame* aFrame, TransformReferenceBox& aRefBox) {
+ MOZ_ASSERT(aFrame);
+
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+
+ // FIXME: It's possible to refactor the calculation of transform-origin, so we
+ // could calculate from the caller, and reuse the value in nsDisplayList.cpp.
+ CSSPoint transformOrigin = nsStyleTransformMatrix::Convert2DPosition(
+ display->mTransformOrigin.horizontal, display->mTransformOrigin.vertical,
+ aRefBox);
+
+ return ResolveMotionPath(
+ GenerateOffsetPathData(aFrame), display->mOffsetDistance,
+ display->mOffsetRotate, display->mOffsetAnchor, display->mOffsetPosition,
+ transformOrigin, aRefBox, ComputeAnchorPointAdjustment(*aFrame));
+}
+
+// Generate data for motion path on the compositor thread.
+static OffsetPathData GenerateOffsetPathData(
+ const StyleOffsetPath& aOffsetPath,
+ const StyleOffsetPosition& aOffsetPosition,
+ const layers::MotionPathData& aMotionPathData,
+ gfx::Path* aCachedMotionPath) {
+ if (aOffsetPath.IsNone()) {
+ return OffsetPathData::None();
+ }
+
+ // Handle ray().
+ if (aOffsetPath.IsRay()) {
+ return aMotionPathData.coordBox().IsEmpty()
+ ? OffsetPathData::None()
+ : OffsetPathData::Ray(
+ aOffsetPath.AsRay(), aMotionPathData.coordBox(),
+ aMotionPathData.currentPosition(),
+ aMotionPathData.rayContainReferenceLength());
+ }
+
+ // Handle path().
+ // FIXME: Bug 1837042, cache gfx::Path for shapes other than path().
+ if (aOffsetPath.IsPath()) {
+ const StyleSVGPathData& pathData = aOffsetPath.AsSVGPathData();
+ // If aCachedMotionPath is valid, we have a fixed path.
+ // This means we have pre-built it already and no need to update.
+ RefPtr<gfx::Path> path = aCachedMotionPath;
+ if (!path) {
+ RefPtr<gfx::PathBuilder> builder =
+ MotionPathUtils::GetCompositorPathBuilder();
+ path = MotionPathUtils::BuildSVGPath(pathData, builder);
+ }
+ // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
+ // to give it the current box position.
+ return OffsetPathData::Shape(path.forget(), {}, IsClosedLoop(pathData));
+ }
+
+ // The rest part is to handle "<basic-shape> || <coord-box>".
+ MOZ_ASSERT(aOffsetPath.IsBasicShapeOrCoordBox());
+
+ const nsRect& coordBox = aMotionPathData.coordBox();
+ if (coordBox.IsEmpty()) {
+ return OffsetPathData::None();
+ }
+
+ RefPtr<gfx::PathBuilder> builder =
+ MotionPathUtils::GetCompositorPathBuilder();
+ if (!builder) {
+ return OffsetPathData::None();
+ }
+
+ RefPtr<gfx::Path> path;
+ if (aOffsetPath.IsCoordBox()) {
+ const nsRect insetRect = ShapeUtils::ComputeInsetRect(
+ StyleRect<LengthPercentage>::WithAllSides(LengthPercentage::Zero()),
+ coordBox);
+ const nsTArray<nscoord>& radii = aMotionPathData.coordBoxInsetRadii();
+ path = ShapeUtils::BuildRectPath(
+ insetRect, radii.IsEmpty() ? nullptr : radii.Elements(), coordBox,
+ AppUnitsPerCSSPixel(), builder);
+ } else {
+ path = MotionPathUtils::BuildPath(
+ aOffsetPath.AsOffsetPath().path->AsShape(), aOffsetPosition, coordBox,
+ aMotionPathData.currentPosition(), builder);
+ }
+
+ return path ? OffsetPathData::Shape(
+ path.forget(), nsPoint(aMotionPathData.currentPosition()),
+ true)
+ : OffsetPathData::None();
+}
+
+/* static */
+Maybe<ResolvedMotionPathData> MotionPathUtils::ResolveMotionPath(
+ const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance,
+ const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor,
+ const StyleOffsetPosition* aPosition,
+ const Maybe<layers::MotionPathData>& aMotionPathData,
+ TransformReferenceBox& aRefBox, gfx::Path* aCachedMotionPath) {
+ if (!aPath) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(aMotionPathData);
+
+ auto zeroOffsetDistance = LengthPercentage::Zero();
+ auto autoOffsetRotate = StyleOffsetRotate{true, StyleAngle::Zero()};
+ auto autoOffsetAnchor = StylePositionOrAuto::Auto();
+ auto autoOffsetPosition = StyleOffsetPosition::Auto();
+ return ResolveMotionPath(
+ GenerateOffsetPathData(*aPath,
+ aPosition ? *aPosition : autoOffsetPosition,
+ *aMotionPathData, aCachedMotionPath),
+ aDistance ? *aDistance : zeroOffsetDistance,
+ aRotate ? *aRotate : autoOffsetRotate,
+ aAnchor ? *aAnchor : autoOffsetAnchor,
+ aPosition ? *aPosition : autoOffsetPosition, aMotionPathData->origin(),
+ aRefBox, aMotionPathData->anchorAdjustment());
+}
+
+/* static */
+already_AddRefed<gfx::Path> MotionPathUtils::BuildSVGPath(
+ const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) {
+ if (!aPathBuilder) {
+ return nullptr;
+ }
+
+ const Span<const StylePathCommand>& path = aPath._0.AsSpan();
+ return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt,
+ 0.0);
+}
+
+/* static */
+already_AddRefed<gfx::Path> MotionPathUtils::BuildPath(
+ const StyleBasicShape& aBasicShape,
+ const StyleOffsetPosition& aOffsetPosition, const nsRect& aCoordBox,
+ const nsPoint& aCurrentPosition, gfx::PathBuilder* aPathBuilder) {
+ if (!aPathBuilder) {
+ return nullptr;
+ }
+
+ switch (aBasicShape.tag) {
+ case StyleBasicShape::Tag::Circle: {
+ const nsPoint center =
+ ComputePosition(aBasicShape.AsCircle().position, aOffsetPosition,
+ aCoordBox, aCurrentPosition);
+ return ShapeUtils::BuildCirclePath(aBasicShape, aCoordBox, center,
+ AppUnitsPerCSSPixel(), aPathBuilder);
+ }
+ case StyleBasicShape::Tag::Ellipse: {
+ const nsPoint center =
+ ComputePosition(aBasicShape.AsEllipse().position, aOffsetPosition,
+ aCoordBox, aCurrentPosition);
+ return ShapeUtils::BuildEllipsePath(aBasicShape, aCoordBox, center,
+ AppUnitsPerCSSPixel(), aPathBuilder);
+ }
+ case StyleBasicShape::Tag::Rect:
+ return ShapeUtils::BuildInsetPath(aBasicShape, aCoordBox,
+ AppUnitsPerCSSPixel(), aPathBuilder);
+ case StyleBasicShape::Tag::Polygon:
+ return ShapeUtils::BuildPolygonPath(aBasicShape, aCoordBox,
+ AppUnitsPerCSSPixel(), aPathBuilder);
+ case StyleBasicShape::Tag::Path:
+ // FIXME: Bug 1836847. Once we support "at <position>" for path(), we have
+ // to also check its containing block as well. For now, we are still
+ // building its gfx::Path directly by its SVGPathData without other
+ // reference. https://github.com/w3c/fxtf-drafts/issues/504
+ return BuildSVGPath(aBasicShape.AsPath().path, aPathBuilder);
+ }
+
+ return nullptr;
+}
+
+/* static */
+already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetPathBuilder() {
+ // Here we only need to build a valid path for motion path, so
+ // using the default values of stroke-width, stoke-linecap, and fill-rule
+ // is fine for now because what we want is to get the point and its normal
+ // vector along the path, instead of rendering it.
+ RefPtr<gfx::PathBuilder> builder =
+ gfxPlatform::GetPlatform()
+ ->ScreenReferenceDrawTarget()
+ ->CreatePathBuilder(gfx::FillRule::FILL_WINDING);
+ return builder.forget();
+}
+
+/* static */
+already_AddRefed<gfx::PathBuilder> MotionPathUtils::GetCompositorPathBuilder() {
+ // FIXME: Perhaps we need a PathBuilder which is independent on the backend.
+ RefPtr<gfx::PathBuilder> builder =
+ gfxPlatform::Initialized()
+ ? gfxPlatform::GetPlatform()
+ ->ScreenReferenceDrawTarget()
+ ->CreatePathBuilder(gfx::FillRule::FILL_WINDING)
+ : gfx::Factory::CreateSimplePathBuilder();
+ return builder.forget();
+}
+
+} // namespace mozilla
diff --git a/layout/base/MotionPathUtils.h b/layout/base/MotionPathUtils.h
new file mode 100644
index 0000000000..cf4d820016
--- /dev/null
+++ b/layout/base/MotionPathUtils.h
@@ -0,0 +1,264 @@
+/* -*- 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_MotionPathUtils_h
+#define mozilla_MotionPathUtils_h
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ServoStyleConsts.h"
+#include "Units.h"
+
+class nsIFrame;
+
+namespace nsStyleTransformMatrix {
+class TransformReferenceBox;
+}
+
+namespace mozilla {
+
+namespace layers {
+class MotionPathData;
+class PathCommand;
+} // namespace layers
+
+struct ResolvedMotionPathData {
+ gfx::Point mTranslate;
+ float mRotate;
+ // The delta value between transform-origin and offset-anchor.
+ gfx::Point mShift;
+};
+
+// The collected information for offset-path. We preprocess the value of
+// offset-path and use this data for resolving motion path.
+struct OffsetPathData {
+ enum class Type : uint8_t {
+ None,
+ Shape,
+ Ray,
+ };
+
+ struct ShapeData {
+ RefPtr<gfx::Path> mGfxPath;
+ // The current position of this transfromed box in the coordinate system of
+ // its containing block.
+ nsPoint mCurrentPosition;
+ // True if it is a closed loop; false if it is a unclosed interval.
+ // https://drafts.fxtf.org/motion/#path-distance
+ bool mIsClosedLoop;
+ };
+
+ struct RayData {
+ const StyleRayFunction* mRay;
+ // The coord box of the containing block.
+ nsRect mCoordBox;
+ // The current position of this transfromed box in the coordinate system of
+ // its containing block.
+ nsPoint mCurrentPosition;
+ // The reference length for computing ray(contain).
+ CSSCoord mContainReferenceLength;
+ };
+
+ Type mType;
+ union {
+ ShapeData mShape;
+ RayData mRay;
+ };
+
+ static OffsetPathData None() { return OffsetPathData(); }
+ static OffsetPathData Shape(already_AddRefed<gfx::Path>&& aGfxPath,
+ nsPoint&& aCurrentPosition, bool aIsClosedPath) {
+ return OffsetPathData(std::move(aGfxPath), std::move(aCurrentPosition),
+ aIsClosedPath);
+ }
+ static OffsetPathData Ray(const StyleRayFunction& aRay, nsRect&& aCoordBox,
+ nsPoint&& aPosition,
+ CSSCoord&& aContainReferenceLength) {
+ return OffsetPathData(&aRay, std::move(aCoordBox), std::move(aPosition),
+ std::move(aContainReferenceLength));
+ }
+ static OffsetPathData Ray(const StyleRayFunction& aRay,
+ const nsRect& aCoordBox, const nsPoint& aPosition,
+ const CSSCoord& aContainReferenceLength) {
+ return OffsetPathData(&aRay, aCoordBox, aPosition, aContainReferenceLength);
+ }
+
+ bool IsNone() const { return mType == Type::None; }
+ bool IsShape() const { return mType == Type::Shape; }
+ bool IsRay() const { return mType == Type::Ray; }
+
+ const ShapeData& AsShape() const {
+ MOZ_ASSERT(IsShape());
+ return mShape;
+ }
+
+ const RayData& AsRay() const {
+ MOZ_ASSERT(IsRay());
+ return mRay;
+ }
+
+ ~OffsetPathData() {
+ switch (mType) {
+ case Type::Shape:
+ mShape.~ShapeData();
+ break;
+ case Type::Ray:
+ mRay.~RayData();
+ break;
+ default:
+ break;
+ }
+ }
+
+ OffsetPathData(const OffsetPathData& aOther) : mType(aOther.mType) {
+ switch (mType) {
+ case Type::Shape:
+ mShape = aOther.mShape;
+ break;
+ case Type::Ray:
+ mRay = aOther.mRay;
+ break;
+ default:
+ break;
+ }
+ }
+
+ OffsetPathData(OffsetPathData&& aOther) : mType(aOther.mType) {
+ switch (mType) {
+ case Type::Shape:
+ mShape = std::move(aOther.mShape);
+ break;
+ case Type::Ray:
+ mRay = std::move(aOther.mRay);
+ break;
+ default:
+ break;
+ }
+ }
+
+ private:
+ OffsetPathData() : mType(Type::None) {}
+ OffsetPathData(already_AddRefed<gfx::Path>&& aPath,
+ nsPoint&& aCurrentPosition, bool aIsClosed)
+ : mType(Type::Shape),
+ mShape{std::move(aPath), std::move(aCurrentPosition), aIsClosed} {}
+ OffsetPathData(const StyleRayFunction* aRay, nsRect&& aCoordBox,
+ nsPoint&& aPosition, CSSCoord&& aContainReferenceLength)
+ : mType(Type::Ray),
+ mRay{aRay, std::move(aCoordBox), std::move(aPosition),
+ std::move(aContainReferenceLength)} {}
+ OffsetPathData(const StyleRayFunction* aRay, const nsRect& aCoordBox,
+ const nsPoint& aPosition,
+ const CSSCoord& aContainReferenceLength)
+ : mType(Type::Ray),
+ mRay{aRay, aCoordBox, aPosition, aContainReferenceLength} {}
+ OffsetPathData& operator=(const OffsetPathData&) = delete;
+ OffsetPathData& operator=(OffsetPathData&&) = delete;
+};
+
+// MotionPathUtils is a namespace class containing utility functions related to
+// processing motion path in the [motion-1].
+// https://drafts.fxtf.org/motion-1/
+class MotionPathUtils final {
+ using TransformReferenceBox = nsStyleTransformMatrix::TransformReferenceBox;
+
+ public:
+ /**
+ * SVG frames (unlike other frames) have a reference box that can be (and
+ * typically is) offset from the TopLeft() of the frame.
+ *
+ * In motion path, we have to make sure the object is aligned with offset-path
+ * when using content area, so we should tweak the anchor point by a given
+ * offset.
+ */
+ static CSSPoint ComputeAnchorPointAdjustment(const nsIFrame& aFrame);
+
+ /**
+ * In CSS context, this returns the the box being referenced from the element
+ * that establishes the containing block for this element.
+ * In SVG context, we always use view-box.
+ * https://drafts.fxtf.org/motion-1/#valdef-offset-path-coord-box
+ */
+ static const nsIFrame* GetOffsetPathReferenceBox(const nsIFrame* aFrame,
+ nsRect& aOutputRect);
+
+ /**
+ * Return the width or the height of the element’s border box, whichever is
+ * larger. This is for computing the ray() with "contain" keyword.
+ */
+ static CSSCoord GetRayContainReferenceSize(nsIFrame* aFrame);
+
+ /**
+ * Get the resolved radius for inset(0 round X), where X is the parameter of
+ * |aRadius|.
+ * This returns an empty array if we cannot compute the radii; otherwise, it
+ * returns an array with 8 elements.
+ */
+ static nsTArray<nscoord> ComputeBorderRadii(
+ const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox);
+
+ /**
+ * Generate the motion path transform result. This function may be called on
+ * the compositor thread.
+ */
+ static Maybe<ResolvedMotionPathData> ResolveMotionPath(
+ const OffsetPathData& aPath, const LengthPercentage& aDistance,
+ const StyleOffsetRotate& aRotate, const StylePositionOrAuto& aAnchor,
+ const StyleOffsetPosition& aPosition, const CSSPoint& aTransformOrigin,
+ TransformReferenceBox&, const CSSPoint& aAnchorPointAdjustment);
+
+ /**
+ * Generate the motion path transform result with |nsIFrame|. This is only
+ * called in the main thread.
+ */
+ static Maybe<ResolvedMotionPathData> ResolveMotionPath(
+ const nsIFrame* aFrame, TransformReferenceBox&);
+
+ /**
+ * Generate the motion path transfrom result with styles and
+ * layers::MotionPathData.
+ * This is only called by the compositor.
+ */
+ static Maybe<ResolvedMotionPathData> ResolveMotionPath(
+ const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance,
+ const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor,
+ const StyleOffsetPosition* aPosition,
+ const Maybe<layers::MotionPathData>& aMotionPathData,
+ TransformReferenceBox&, gfx::Path* aCachedMotionPath);
+
+ /**
+ * Build a gfx::Path from the svg path data. We should give it a path builder.
+ * If |aPathBuilder| is nullptr, we return null path.
+ * This can be used on the main thread or on the compositor thread.
+ */
+ static already_AddRefed<gfx::Path> BuildSVGPath(
+ const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder);
+
+ /**
+ * Build a gfx::Path from the computed basic shape.
+ */
+ static already_AddRefed<gfx::Path> BuildPath(const StyleBasicShape&,
+ const StyleOffsetPosition&,
+ const nsRect& aCoordBox,
+ const nsPoint& aCurrentPosition,
+ gfx::PathBuilder*);
+
+ /**
+ * Get a path builder for motion path on the main thread.
+ */
+ static already_AddRefed<gfx::PathBuilder> GetPathBuilder();
+
+ /**
+ * Get a path builder for compositor.
+ */
+ static already_AddRefed<gfx::PathBuilder> GetCompositorPathBuilder();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_MotionPathUtils_h
diff --git a/layout/base/OverflowChangedTracker.h b/layout/base/OverflowChangedTracker.h
new file mode 100644
index 0000000000..f79962e82f
--- /dev/null
+++ b/layout/base/OverflowChangedTracker.h
@@ -0,0 +1,208 @@
+/* -*- 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_OverflowChangedTracker_h
+#define mozilla_OverflowChangedTracker_h
+
+#include "nsIFrame.h"
+#include "nsContainerFrame.h"
+#include "mozilla/SplayTree.h"
+
+namespace mozilla {
+
+/**
+ * Helper class that collects a list of frames that need
+ * UpdateOverflow() called on them, and coalesces them
+ * to avoid walking up the same ancestor tree multiple times.
+ */
+class OverflowChangedTracker {
+ public:
+ enum ChangeKind {
+ /**
+ * The frame was explicitly added as a result of
+ * nsChangeHint_UpdatePostTransformOverflow and hence may have had a style
+ * change that changes its geometry relative to parent, without reflowing.
+ */
+ TRANSFORM_CHANGED,
+ /**
+ * The overflow areas of children have changed
+ * and we need to call UpdateOverflow on the frame.
+ */
+ CHILDREN_CHANGED,
+ };
+
+ OverflowChangedTracker() : mSubtreeRoot(nullptr) {}
+
+ ~OverflowChangedTracker() {
+ NS_ASSERTION(mEntryList.empty(), "Need to flush before destroying!");
+ }
+
+ /**
+ * Add a frame that has had a style change, and needs its
+ * overflow updated.
+ *
+ * If there are pre-transform overflow areas stored for this
+ * frame, then we will call FinishAndStoreOverflow with those
+ * areas instead of UpdateOverflow().
+ *
+ * If the overflow area changes, then UpdateOverflow will also
+ * be called on the parent.
+ */
+ void AddFrame(nsIFrame* aFrame, ChangeKind aChangeKind) {
+ MOZ_ASSERT(
+ aFrame->FrameMaintainsOverflow(),
+ "Why add a frame that doesn't maintain overflow to the tracker?");
+ uint32_t depth = aFrame->GetDepthInFrameTree();
+ Entry* entry = nullptr;
+ if (!mEntryList.empty()) {
+ entry = mEntryList.find(Entry(aFrame, depth));
+ }
+ if (entry == nullptr) {
+ // Add new entry.
+ mEntryList.insert(new Entry(aFrame, depth, aChangeKind));
+ } else {
+ // Update the existing entry if the new value is stronger.
+ entry->mChangeKind = std::max(entry->mChangeKind, aChangeKind);
+ }
+ }
+
+ /**
+ * Remove a frame.
+ */
+ void RemoveFrame(nsIFrame* aFrame) {
+ if (mEntryList.empty()) {
+ return;
+ }
+
+ uint32_t depth = aFrame->GetDepthInFrameTree();
+ if (mEntryList.find(Entry(aFrame, depth))) {
+ delete mEntryList.remove(Entry(aFrame, depth));
+ }
+ }
+
+ /**
+ * Set the subtree root to limit overflow updates. This must be set if and
+ * only if currently reflowing aSubtreeRoot, to ensure overflow changes will
+ * still propagate correctly.
+ */
+ void SetSubtreeRoot(const nsIFrame* aSubtreeRoot) {
+ mSubtreeRoot = aSubtreeRoot;
+ }
+
+ /**
+ * Update the overflow of all added frames, and clear the entry list.
+ *
+ * Start from those deepest in the frame tree and works upwards. This stops
+ * us from processing the same frame twice.
+ */
+ void Flush() {
+ while (!mEntryList.empty()) {
+ Entry* entry = mEntryList.removeMin();
+ nsIFrame* frame = entry->mFrame;
+
+ bool overflowChanged = false;
+ if (entry->mChangeKind == CHILDREN_CHANGED) {
+ // Need to union the overflow areas of the children.
+ // Only update the parent if the overflow changes.
+ overflowChanged = frame->UpdateOverflow();
+ } else {
+ // Take a faster path that doesn't require unioning the overflow areas
+ // of our children.
+
+ NS_ASSERTION(
+ frame->GetProperty(nsIFrame::DebugInitialOverflowPropertyApplied()),
+ "InitialOverflowProperty must be set first.");
+
+ OverflowAreas* overflow =
+ frame->GetProperty(nsIFrame::InitialOverflowProperty());
+ if (overflow) {
+ // FinishAndStoreOverflow will change the overflow areas passed in,
+ // so make a copy.
+ OverflowAreas overflowCopy = *overflow;
+ frame->FinishAndStoreOverflow(overflowCopy, frame->GetSize());
+ } else {
+ nsRect bounds(nsPoint(0, 0), frame->GetSize());
+ OverflowAreas boundsOverflow;
+ boundsOverflow.SetAllTo(bounds);
+ frame->FinishAndStoreOverflow(boundsOverflow, bounds.Size());
+ }
+
+ // We can't tell if the overflow changed, so be conservative
+ overflowChanged = true;
+ }
+
+ // If the frame style changed (e.g. positioning offsets)
+ // then we need to update the parent with the overflow areas of its
+ // children.
+ if (overflowChanged) {
+ nsIFrame* parent = frame->GetParent();
+
+ // It's possible that the parent is already in a nondisplay context,
+ // should not add it to the list if that's true.
+ if (parent && parent != mSubtreeRoot &&
+ parent->FrameMaintainsOverflow()) {
+ Entry* parentEntry =
+ mEntryList.find(Entry(parent, entry->mDepth - 1));
+ if (parentEntry) {
+ parentEntry->mChangeKind =
+ std::max(parentEntry->mChangeKind, CHILDREN_CHANGED);
+ } else {
+ mEntryList.insert(
+ new Entry(parent, entry->mDepth - 1, CHILDREN_CHANGED));
+ }
+ }
+ }
+ delete entry;
+ }
+ }
+
+ private:
+ struct Entry : SplayTreeNode<Entry> {
+ Entry(nsIFrame* aFrame, uint32_t aDepth,
+ ChangeKind aChangeKind = CHILDREN_CHANGED)
+ : mFrame(aFrame), mDepth(aDepth), mChangeKind(aChangeKind) {}
+
+ bool operator==(const Entry& aOther) const {
+ return mFrame == aOther.mFrame;
+ }
+
+ /**
+ * Sort by *reverse* depth in the tree, and break ties with
+ * the frame pointer.
+ */
+ bool operator<(const Entry& aOther) const {
+ if (mDepth == aOther.mDepth) {
+ return mFrame < aOther.mFrame;
+ }
+ return mDepth > aOther.mDepth; /* reverse, want "min" to be deepest */
+ }
+
+ static int compare(const Entry& aOne, const Entry& aTwo) {
+ if (aOne == aTwo) {
+ return 0;
+ } else if (aOne < aTwo) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ nsIFrame* mFrame;
+ /* Depth in the frame tree */
+ uint32_t mDepth;
+ ChangeKind mChangeKind;
+ };
+
+ /* A list of frames to process, sorted by their depth in the frame tree */
+ SplayTree<Entry, Entry> mEntryList;
+
+ /* Don't update overflow of this frame or its ancestors. */
+ const nsIFrame* mSubtreeRoot;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/PositionedEventTargeting.cpp b/layout/base/PositionedEventTargeting.cpp
new file mode 100644
index 0000000000..e159239f16
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.cpp
@@ -0,0 +1,619 @@
+/* -*- 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 "PositionedEventTargeting.h"
+
+#include "mozilla/EventListenerManager.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/ToString.h"
+#include "mozilla/dom/MouseEventBinding.h"
+#include "nsContainerFrame.h"
+#include "nsFrameList.h" // for DEBUG_FRAME_DUMP
+#include "nsHTMLParts.h"
+#include "nsLayoutUtils.h"
+#include "nsGkAtoms.h"
+#include "nsFontMetrics.h"
+#include "nsPrintfCString.h"
+#include "mozilla/dom/Element.h"
+#include "nsRegion.h"
+#include "nsDeviceContext.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+// If debugging this code you may wish to enable this logging, via
+// the env var MOZ_LOG="event.retarget:4". For extra logging (getting
+// frame dumps, use MOZ_LOG="event.retarget:5".
+static mozilla::LazyLogModule sEvtTgtLog("event.retarget");
+#define PET_LOG(...) MOZ_LOG(sEvtTgtLog, LogLevel::Debug, (__VA_ARGS__))
+
+namespace mozilla {
+
+/*
+ * The basic goal of FindFrameTargetedByInputEvent() is to find a good
+ * target element that can respond to mouse events. Both mouse events and touch
+ * events are targeted at this element. Note that even for touch events, we
+ * check responsiveness to mouse events. We assume Web authors
+ * designing for touch events will take their own steps to account for
+ * inaccurate touch events.
+ *
+ * GetClickableAncestor() encapsulates the heuristic that determines whether an
+ * element is expected to respond to mouse events. An element is deemed
+ * "clickable" if it has registered listeners for "click", "mousedown" or
+ * "mouseup", or is on a whitelist of element tags (<a>, <button>, <input>,
+ * <select>, <textarea>, <label>), or has role="button", or is a link, or
+ * is a suitable XUL element.
+ * Any descendant (in the same document) of a clickable element is also
+ * deemed clickable since events will propagate to the clickable element from
+ * its descendant.
+ *
+ * If the element directly under the event position is clickable (or
+ * event radii are disabled), we always use that element. Otherwise we collect
+ * all frames intersecting a rectangle around the event position (taking CSS
+ * transforms into account) and choose the best candidate in GetClosest().
+ * Only GetClickableAncestor() candidates are considered; if none are found,
+ * then we revert to targeting the element under the event position.
+ * We ignore candidates outside the document subtree rooted by the
+ * document of the element directly under the event position. This ensures that
+ * event listeners in ancestor documents don't make it completely impossible
+ * to target a non-clickable element in a child document.
+ *
+ * When both a frame and its ancestor are in the candidate list, we ignore
+ * the ancestor. Otherwise a large ancestor element with a mouse event listener
+ * and some descendant elements that need to be individually targetable would
+ * disable intelligent targeting of those descendants within its bounds.
+ *
+ * GetClosest() computes the transformed axis-aligned bounds of each
+ * candidate frame, then computes the Manhattan distance from the event point
+ * to the bounds rect (which can be zero). The frame with the
+ * shortest distance is chosen. For visited links we multiply the distance
+ * by a specified constant weight; this can be used to make visited links
+ * more or less likely to be targeted than non-visited links.
+ */
+
+// Enum that determines which type of elements to count as targets in the
+// search. Clickable elements are generally ones that respond to click events,
+// like form inputs and links and things with click event listeners.
+// Touchable elements are a much narrower set of elements; ones with touchstart
+// and touchend listeners.
+enum class SearchType {
+ None,
+ Clickable,
+ Touchable,
+};
+
+struct EventRadiusPrefs {
+ bool mEnabled; // other fields are valid iff this field is true
+ uint32_t mVisitedWeight; // in percent, i.e. default is 100
+ uint32_t mRadiusTopmm;
+ uint32_t mRadiusRightmm;
+ uint32_t mRadiusBottommm;
+ uint32_t mRadiusLeftmm;
+ bool mTouchOnly;
+ bool mReposition;
+ SearchType mSearchType;
+
+ explicit EventRadiusPrefs(EventClassID aEventClassID) {
+ if (aEventClassID == eTouchEventClass) {
+ mEnabled = StaticPrefs::ui_touch_radius_enabled();
+ mVisitedWeight = StaticPrefs::ui_touch_radius_visitedWeight();
+ mRadiusTopmm = StaticPrefs::ui_touch_radius_topmm();
+ mRadiusRightmm = StaticPrefs::ui_touch_radius_rightmm();
+ mRadiusBottommm = StaticPrefs::ui_touch_radius_bottommm();
+ mRadiusLeftmm = StaticPrefs::ui_touch_radius_leftmm();
+ mTouchOnly = false; // Always false, unlike mouse events.
+ mReposition = false; // Always false, unlike mouse events.
+ mSearchType = SearchType::Touchable;
+
+ } else if (aEventClassID == eMouseEventClass) {
+ mEnabled = StaticPrefs::ui_mouse_radius_enabled();
+ mVisitedWeight = StaticPrefs::ui_mouse_radius_visitedWeight();
+ mRadiusTopmm = StaticPrefs::ui_mouse_radius_topmm();
+ mRadiusRightmm = StaticPrefs::ui_mouse_radius_rightmm();
+ mRadiusBottommm = StaticPrefs::ui_mouse_radius_bottommm();
+ mRadiusLeftmm = StaticPrefs::ui_mouse_radius_leftmm();
+ mTouchOnly = StaticPrefs::ui_mouse_radius_inputSource_touchOnly();
+ mReposition = StaticPrefs::ui_mouse_radius_reposition();
+ mSearchType = SearchType::Clickable;
+
+ } else {
+ mEnabled = false;
+ mVisitedWeight = 0;
+ mRadiusTopmm = 0;
+ mRadiusRightmm = 0;
+ mRadiusBottommm = 0;
+ mRadiusLeftmm = 0;
+ mTouchOnly = false;
+ mReposition = false;
+ mSearchType = SearchType::None;
+ }
+ }
+};
+
+static bool HasMouseListener(nsIContent* aContent) {
+ if (EventListenerManager* elm = aContent->GetExistingListenerManager()) {
+ return elm->HasListenersFor(nsGkAtoms::onclick) ||
+ elm->HasListenersFor(nsGkAtoms::onmousedown) ||
+ elm->HasListenersFor(nsGkAtoms::onmouseup);
+ }
+
+ return false;
+}
+
+static bool HasTouchListener(nsIContent* aContent) {
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ // FIXME: Should this really use the pref rather than TouchEvent::PrefEnabled
+ // or such?
+ if (!StaticPrefs::dom_w3c_touch_events_enabled()) {
+ return false;
+ }
+
+ return elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchstart) ||
+ elm->HasNonSystemGroupListenersFor(nsGkAtoms::ontouchend);
+}
+
+static bool HasPointerListener(nsIContent* aContent) {
+ EventListenerManager* elm = aContent->GetExistingListenerManager();
+ if (!elm) {
+ return false;
+ }
+
+ return elm->HasListenersFor(nsGkAtoms::onpointerdown) ||
+ elm->HasListenersFor(nsGkAtoms::onpointerup);
+}
+
+static bool IsDescendant(nsIFrame* aFrame, nsIContent* aAncestor,
+ nsAutoString* aLabelTargetId) {
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aLabelTargetId && content->IsHTMLElement(nsGkAtoms::label)) {
+ content->AsElement()->GetAttr(nsGkAtoms::_for, *aLabelTargetId);
+ }
+ if (content == aAncestor) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static nsIContent* GetTouchableAncestor(nsIFrame* aFrame,
+ nsAtom* aStopAt = nullptr) {
+ // Input events propagate up the content tree so we'll follow the content
+ // ancestors to look for elements accepting the touch event.
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aStopAt && content->IsHTMLElement(aStopAt)) {
+ break;
+ }
+ if (HasTouchListener(content)) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nsIContent* GetClickableAncestor(
+ nsIFrame* aFrame, nsAtom* aStopAt = nullptr,
+ nsAutoString* aLabelTargetId = nullptr) {
+ // If the frame is `cursor:pointer` or inherits `cursor:pointer` from an
+ // ancestor, treat it as clickable. This is a heuristic to deal with pages
+ // where the click event listener is on the <body> or <html> element but it
+ // triggers an action on some specific element. We want the specific element
+ // to be considered clickable, and at least some pages that do this indicate
+ // the clickability by setting `cursor:pointer`, so we use that here.
+ // Note that descendants of `cursor:pointer` elements that override the
+ // inherited `pointer` to `auto` or any other value are NOT treated as
+ // clickable, because it seems like the content author is trying to express
+ // non-clickability on that sub-element.
+ // In the future depending on real-world cases it might make sense to expand
+ // this check to any non-auto cursor. Such a change would also pick up things
+ // like contenteditable or input fields, which can then be removed from the
+ // loop below, and would have better performance.
+ if (aFrame->StyleUI()->Cursor().keyword == StyleCursorKind::Pointer) {
+ return aFrame->GetContent();
+ }
+
+ // Input events propagate up the content tree so we'll follow the content
+ // ancestors to look for elements accepting the click.
+ for (nsIContent* content = aFrame->GetContent(); content;
+ content = content->GetFlattenedTreeParent()) {
+ if (aStopAt && content->IsHTMLElement(aStopAt)) {
+ break;
+ }
+ if (HasTouchListener(content) || HasMouseListener(content) ||
+ HasPointerListener(content)) {
+ return content;
+ }
+ if (content->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input,
+ nsGkAtoms::select, nsGkAtoms::textarea)) {
+ return content;
+ }
+ if (content->IsHTMLElement(nsGkAtoms::label)) {
+ if (aLabelTargetId) {
+ content->AsElement()->GetAttr(nsGkAtoms::_for, *aLabelTargetId);
+ }
+ return content;
+ }
+
+ // Bug 921928: we don't have access to the content of remote iframe.
+ // So fluffing won't go there. We do an optimistic assumption here:
+ // that the content of the remote iframe needs to be a target.
+ if (content->IsHTMLElement(nsGkAtoms::iframe) &&
+ content->AsElement()->AttrValueIs(kNameSpaceID_None,
+ nsGkAtoms::mozbrowser,
+ nsGkAtoms::_true, eIgnoreCase) &&
+ content->AsElement()->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
+ nsGkAtoms::_true, eIgnoreCase)) {
+ return content;
+ }
+
+ // See nsCSSFrameConstructor::FindXULTagData. This code is not
+ // really intended to be used with XUL, though.
+ if (content->IsAnyOfXULElements(
+ nsGkAtoms::button, nsGkAtoms::checkbox, nsGkAtoms::radio,
+ nsGkAtoms::menu, nsGkAtoms::menuitem, nsGkAtoms::menulist,
+ nsGkAtoms::scrollbarbutton, nsGkAtoms::resizer)) {
+ return content;
+ }
+
+ static Element::AttrValuesArray clickableRoles[] = {
+ nsGkAtoms::button, nsGkAtoms::key, nullptr};
+ if (auto* element = Element::FromNode(*content)) {
+ if (element->IsLink()) {
+ return content;
+ }
+ if (element->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::role,
+ clickableRoles, eIgnoreCase) >= 0) {
+ return content;
+ }
+ }
+ if (content->IsEditable()) {
+ return content;
+ }
+ }
+ return nullptr;
+}
+
+static nscoord AppUnitsFromMM(RelativeTo aFrame, uint32_t aMM) {
+ nsPresContext* pc = aFrame.mFrame->PresContext();
+ float result = float(aMM) * (pc->DeviceContext()->AppUnitsPerPhysicalInch() /
+ MM_PER_INCH_FLOAT);
+ if (aFrame.mViewportType == ViewportType::Layout) {
+ PresShell* presShell = pc->PresShell();
+ result = result / presShell->GetResolution();
+ }
+ return NSToCoordRound(result);
+}
+
+/**
+ * Clip aRect with the bounds of aFrame in the coordinate system of
+ * aRootFrame. aRootFrame is an ancestor of aFrame.
+ */
+static nsRect ClipToFrame(RelativeTo aRootFrame, const nsIFrame* aFrame,
+ nsRect& aRect) {
+ nsRect bound = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, nsRect(nsPoint(0, 0), aFrame->GetSize()), aRootFrame);
+ nsRect result = bound.Intersect(aRect);
+ return result;
+}
+
+static nsRect GetTargetRect(RelativeTo aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame,
+ const nsIFrame* aRestrictToDescendants,
+ const EventRadiusPrefs& aPrefs, uint32_t aFlags) {
+ nsMargin m(AppUnitsFromMM(aRootFrame, aPrefs.mRadiusTopmm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusRightmm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusBottommm),
+ AppUnitsFromMM(aRootFrame, aPrefs.mRadiusLeftmm));
+ nsRect r(aPointRelativeToRootFrame, nsSize(0, 0));
+ r.Inflate(m);
+ if (!(aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME)) {
+ // Don't clip this rect to the root scroll frame if the flag to ignore the
+ // root scroll frame is set. Note that the GetClosest code will still
+ // enforce that the target found is a descendant of aRestrictToDescendants.
+ r = ClipToFrame(aRootFrame, aRestrictToDescendants, r);
+ }
+ return r;
+}
+
+static float ComputeDistanceFromRect(const nsPoint& aPoint,
+ const nsRect& aRect) {
+ nscoord dx =
+ std::max(0, std::max(aRect.x - aPoint.x, aPoint.x - aRect.XMost()));
+ nscoord dy =
+ std::max(0, std::max(aRect.y - aPoint.y, aPoint.y - aRect.YMost()));
+ return float(NS_hypot(dx, dy));
+}
+
+static float ComputeDistanceFromRegion(const nsPoint& aPoint,
+ const nsRegion& aRegion) {
+ MOZ_ASSERT(!aRegion.IsEmpty(),
+ "can't compute distance between point and empty region");
+ float minDist = -1;
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ float dist = ComputeDistanceFromRect(aPoint, iter.Get());
+ if (dist < minDist || minDist < 0) {
+ minDist = dist;
+ }
+ }
+ return minDist;
+}
+
+// Subtract aRegion from aExposedRegion as long as that doesn't make the
+// exposed region get too complex or removes a big chunk of the exposed region.
+static void SubtractFromExposedRegion(nsRegion* aExposedRegion,
+ const nsRegion& aRegion) {
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+
+ nsRegion tmp;
+ tmp.Sub(*aExposedRegion, aRegion);
+ // Don't let *aExposedRegion get too complex, but don't let it fluff out to
+ // its bounds either. Do let aExposedRegion get more complex if by doing so
+ // we reduce its area by at least half.
+ if (tmp.GetNumRects() <= 15 || tmp.Area() <= aExposedRegion->Area() / 2) {
+ *aExposedRegion = tmp;
+ }
+}
+
+static nsIFrame* GetClosest(RelativeTo aRoot,
+ const nsPoint& aPointRelativeToRootFrame,
+ const nsRect& aTargetRect,
+ const EventRadiusPrefs& aPrefs,
+ const nsIFrame* aRestrictToDescendants,
+ nsIContent* aClickableAncestor,
+ nsTArray<nsIFrame*>& aCandidates) {
+ nsIFrame* bestTarget = nullptr;
+ // Lower is better; distance is in appunits
+ float bestDistance = 1e6f;
+ nsRegion exposedRegion(aTargetRect);
+ for (uint32_t i = 0; i < aCandidates.Length(); ++i) {
+ nsIFrame* f = aCandidates[i];
+
+ bool preservesAxisAlignedRectangles = false;
+ nsRect borderBox = nsLayoutUtils::TransformFrameRectToAncestor(
+ f, nsRect(nsPoint(0, 0), f->GetSize()), aRoot,
+ &preservesAxisAlignedRectangles);
+ PET_LOG("Checking candidate %p with border box %s\n", f,
+ ToString(borderBox).c_str());
+ nsRegion region;
+ region.And(exposedRegion, borderBox);
+ if (region.IsEmpty()) {
+ PET_LOG(" candidate %p had empty hit region\n", f);
+ continue;
+ }
+
+ if (preservesAxisAlignedRectangles) {
+ // Subtract from the exposed region if we have a transform that won't make
+ // the bounds include a bunch of area that we don't actually cover.
+ SubtractFromExposedRegion(&exposedRegion, region);
+ }
+
+ nsAutoString labelTargetId;
+ if (aClickableAncestor &&
+ !IsDescendant(f, aClickableAncestor, &labelTargetId)) {
+ PET_LOG(" candidate %p is not a descendant of required ancestor\n", f);
+ continue;
+ }
+
+ if (aPrefs.mSearchType == SearchType::Clickable) {
+ nsIContent* clickableContent =
+ GetClickableAncestor(f, nsGkAtoms::body, &labelTargetId);
+ if (!aClickableAncestor && !clickableContent) {
+ PET_LOG(" candidate %p was not clickable\n", f);
+ continue;
+ }
+ } else if (aPrefs.mSearchType == SearchType::Touchable) {
+ nsIContent* touchableContent = GetTouchableAncestor(f, nsGkAtoms::body);
+ if (!touchableContent) {
+ PET_LOG(" candidate %p was not touchable\n", f);
+ continue;
+ }
+ }
+
+ // If our current closest frame is a descendant of 'f', skip 'f' (prefer
+ // the nested frame).
+ if (bestTarget && nsLayoutUtils::IsProperAncestorFrameCrossDoc(
+ f, bestTarget, aRoot.mFrame)) {
+ PET_LOG(" candidate %p was ancestor for bestTarget %p\n", f, bestTarget);
+ continue;
+ }
+ if (!aClickableAncestor && !nsLayoutUtils::IsAncestorFrameCrossDoc(
+ aRestrictToDescendants, f, aRoot.mFrame)) {
+ PET_LOG(" candidate %p was not descendant of restrictroot %p\n", f,
+ aRestrictToDescendants);
+ continue;
+ }
+
+ // distance is in appunits
+ float distance =
+ ComputeDistanceFromRegion(aPointRelativeToRootFrame, region);
+ nsIContent* content = f->GetContent();
+ if (content && content->IsElement() &&
+ content->AsElement()->State().HasState(
+ ElementState(ElementState::VISITED))) {
+ distance *= aPrefs.mVisitedWeight / 100.0f;
+ }
+ if (distance < bestDistance) {
+ PET_LOG(" candidate %p is the new best\n", f);
+ bestDistance = distance;
+ bestTarget = f;
+ }
+ }
+ return bestTarget;
+}
+
+// Walk from aTarget up to aRoot, and return the first frame found with an
+// explicit z-index set on it. If no such frame is found, aRoot is returned.
+static const nsIFrame* FindZIndexAncestor(const nsIFrame* aTarget,
+ const nsIFrame* aRoot) {
+ const nsIFrame* candidate = aTarget;
+ while (candidate && candidate != aRoot) {
+ if (candidate->ZIndex().valueOr(0) > 0) {
+ PET_LOG("Restricting search to z-index root %p\n", candidate);
+ return candidate;
+ }
+ candidate = candidate->GetParent();
+ }
+ return aRoot;
+}
+
+nsIFrame* FindFrameTargetedByInputEvent(
+ WidgetGUIEvent* aEvent, RelativeTo aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags) {
+ using FrameForPointOption = nsLayoutUtils::FrameForPointOption;
+ EnumSet<FrameForPointOption> options;
+ if (aFlags & INPUT_IGNORE_ROOT_SCROLL_FRAME) {
+ options += FrameForPointOption::IgnoreRootScrollFrame;
+ }
+ nsIFrame* target = nsLayoutUtils::GetFrameForPoint(
+ aRootFrame, aPointRelativeToRootFrame, options);
+ PET_LOG(
+ "Found initial target %p for event class %s message %s point %s "
+ "relative to root frame %s\n",
+ target, ToChar(aEvent->mClass), ToChar(aEvent->mMessage),
+ ToString(aPointRelativeToRootFrame).c_str(),
+ ToString(aRootFrame).c_str());
+
+ EventRadiusPrefs prefs(aEvent->mClass);
+ if (!prefs.mEnabled || EventRetargetSuppression::IsActive()) {
+ PET_LOG("Retargeting disabled\n");
+ return target;
+ }
+
+ // Do not modify targeting for actual mouse hardware; only for mouse
+ // events generated by touch-screen hardware.
+ if (aEvent->mClass == eMouseEventClass && prefs.mTouchOnly &&
+ aEvent->AsMouseEvent()->mInputSource !=
+ MouseEvent_Binding::MOZ_SOURCE_TOUCH) {
+ PET_LOG("Mouse input event is not from a touch source\n");
+ return target;
+ }
+
+ // If the exact target is non-null, only consider candidate targets in the
+ // same document as the exact target. Otherwise, if an ancestor document has
+ // a mouse event handler for example, targets that are !GetClickableAncestor
+ // can never be targeted --- something nsSubDocumentFrame in an ancestor
+ // document would be targeted instead.
+ const nsIFrame* restrictToDescendants = [&]() -> const nsIFrame* {
+ if (target && target->PresContext() != aRootFrame.mFrame->PresContext()) {
+ return target->PresShell()->GetRootFrame();
+ }
+ return aRootFrame.mFrame;
+ }();
+
+ // Ignore retarget if target is editable.
+ nsIContent* targetContent = target ? target->GetContent() : nullptr;
+ if (targetContent && targetContent->IsEditable()) {
+ PET_LOG("Target %p is editable\n", target);
+ return target;
+ }
+
+ // If the target element inside an element with a z-index, restrict the
+ // search to other elements inside that z-index. This is a heuristic
+ // intended to help with a class of scenarios involving web modals or
+ // web popup type things. In particular it helps alleviate bug 1666792.
+ restrictToDescendants = FindZIndexAncestor(target, restrictToDescendants);
+
+ nsRect targetRect = GetTargetRect(aRootFrame, aPointRelativeToRootFrame,
+ restrictToDescendants, prefs, aFlags);
+ PET_LOG("Expanded point to target rect %s\n", ToString(targetRect).c_str());
+ AutoTArray<nsIFrame*, 8> candidates;
+ nsresult rv = nsLayoutUtils::GetFramesForArea(aRootFrame, targetRect,
+ candidates, options);
+ if (NS_FAILED(rv)) {
+ return target;
+ }
+
+ nsIContent* clickableAncestor = nullptr;
+ if (target) {
+ clickableAncestor = GetClickableAncestor(target, nsGkAtoms::body);
+ if (clickableAncestor) {
+ PET_LOG("Target %p is clickable\n", target);
+ // If the target that was directly hit has a clickable ancestor, that
+ // means it too is clickable. And since it is the same as or a
+ // descendant of clickableAncestor, it should become the root for the
+ // GetClosest search.
+ clickableAncestor = target->GetContent();
+ }
+ }
+
+ nsIFrame* closest =
+ GetClosest(aRootFrame, aPointRelativeToRootFrame, targetRect, prefs,
+ restrictToDescendants, clickableAncestor, candidates);
+ if (closest) {
+ target = closest;
+ }
+
+ PET_LOG("Final target is %p\n", target);
+
+#ifdef DEBUG_FRAME_DUMP
+ // At verbose logging level, dump the frame tree to help with debugging.
+ // Note that dumping the frame tree at the top of the function may flood
+ // logcat on Android devices and cause the PET_LOGs to get dropped.
+ if (MOZ_LOG_TEST(sEvtTgtLog, LogLevel::Verbose)) {
+ if (target) {
+ target->DumpFrameTree();
+ } else {
+ aRootFrame.mFrame->DumpFrameTree();
+ }
+ }
+#endif
+
+ if (!target || !prefs.mReposition) {
+ // No repositioning required for this event
+ return target;
+ }
+
+ // Take the point relative to the root frame, make it relative to the target,
+ // clamp it to the bounds, and then make it relative to the root frame again.
+ nsPoint point = aPointRelativeToRootFrame;
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
+ nsLayoutUtils::TransformPoint(aRootFrame, RelativeTo{target}, point)) {
+ return target;
+ }
+ point = target->GetRectRelativeToSelf().ClampPoint(point);
+ if (nsLayoutUtils::TRANSFORM_SUCCEEDED !=
+ nsLayoutUtils::TransformPoint(RelativeTo{target}, aRootFrame, point)) {
+ return target;
+ }
+ // Now we basically undo the operations in GetEventCoordinatesRelativeTo, to
+ // get back the (now-clamped) coordinates in the event's widget's space.
+ nsView* view = aRootFrame.mFrame->GetView();
+ if (!view) {
+ return target;
+ }
+ LayoutDeviceIntPoint widgetPoint = nsLayoutUtils::TranslateViewToWidget(
+ aRootFrame.mFrame->PresContext(), view, point, aRootFrame.mViewportType,
+ aEvent->mWidget);
+ if (widgetPoint.x != NS_UNCONSTRAINEDSIZE) {
+ // If that succeeded, we update the point in the event
+ aEvent->mRefPoint = widgetPoint;
+ }
+ return target;
+}
+
+uint32_t EventRetargetSuppression::sSuppressionCount = 0;
+
+EventRetargetSuppression::EventRetargetSuppression() { sSuppressionCount++; }
+
+EventRetargetSuppression::~EventRetargetSuppression() { sSuppressionCount--; }
+
+bool EventRetargetSuppression::IsActive() { return sSuppressionCount > 0; }
+
+} // namespace mozilla
diff --git a/layout/base/PositionedEventTargeting.h b/layout/base/PositionedEventTargeting.h
new file mode 100644
index 0000000000..da7313e394
--- /dev/null
+++ b/layout/base/PositionedEventTargeting.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/. */
+
+#ifndef mozilla_PositionedEventTargeting_h
+#define mozilla_PositionedEventTargeting_h
+
+#include <stdint.h>
+#include "mozilla/EventForwards.h"
+#include "RelativeTo.h"
+
+class nsIFrame;
+struct nsPoint;
+
+namespace mozilla {
+
+enum { INPUT_IGNORE_ROOT_SCROLL_FRAME = 0x01 };
+/**
+ * Finds the target frame for a pointer event given the event type and location.
+ * This can look for frames within a rectangle surrounding the actual location
+ * that are suitable targets, to account for inaccurate pointing devices.
+ */
+nsIFrame* FindFrameTargetedByInputEvent(
+ WidgetGUIEvent* aEvent, RelativeTo aRootFrame,
+ const nsPoint& aPointRelativeToRootFrame, uint32_t aFlags = 0);
+
+class MOZ_RAII EventRetargetSuppression {
+ public:
+ EventRetargetSuppression();
+ ~EventRetargetSuppression();
+ static bool IsActive();
+
+ private:
+ static uint32_t sSuppressionCount;
+};
+
+} // namespace mozilla
+
+#endif /* mozilla_PositionedEventTargeting_h */
diff --git a/layout/base/PresShell.cpp b/layout/base/PresShell.cpp
new file mode 100644
index 0000000000..90a9eee411
--- /dev/null
+++ b/layout/base/PresShell.cpp
@@ -0,0 +1,12203 @@
+/* -*- 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 presentation of a document, part 2 */
+
+#include "mozilla/PresShell.h"
+
+#include "Units.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/AncestorIterator.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/dom/ElementBinding.h"
+#include "mozilla/dom/LargestContentfulPaint.h"
+#include "mozilla/dom/PerformanceMainThread.h"
+#include "mozilla/dom/HTMLAreaElement.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/ContentIterator.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/GeckoMVMContext.h"
+#include "mozilla/IMEStateManager.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/PointerLockManager.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/RangeUtils.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "mozilla/StaticAnalysisFunctions.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_font.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_toolkit.h"
+#include "mozilla/Try.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/gfx/Types.h"
+#include <algorithm>
+
+#ifdef XP_WIN
+# include "winuser.h"
+#endif
+
+#include "gfxContext.h"
+#include "gfxUserFontSet.h"
+#include "nsContentList.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "mozilla/dom/BrowserBridgeChild.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/CanonicalBrowsingContext.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PointerEventHandler.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/DOMIntersectionObserver.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/UserActivation.h"
+#include "nsAnimationManager.h"
+#include "nsNameSpaceManager.h" // for Pref-related rule management (bugs 22963,20760,31816)
+#include "nsFlexContainerFrame.h"
+#include "nsIFrame.h"
+#include "nsViewManager.h"
+#include "nsView.h"
+#include "nsCRTGlue.h"
+#include "prinrval.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+#include "nsContainerFrame.h"
+#include "mozilla/dom/Selection.h"
+#include "nsGkAtoms.h"
+#include "nsRange.h"
+#include "nsWindowSizes.h"
+#include "nsCOMPtr.h"
+#include "nsReadableUtils.h"
+#include "nsPageSequenceFrame.h"
+#include "nsCaret.h"
+#include "mozilla/AccessibleCaretEventHub.h"
+#include "nsFrameManager.h"
+#include "nsXPCOM.h"
+#include "nsILayoutHistoryState.h"
+#include "nsILineIterator.h" // for ScrollContentIntoView
+#include "PLDHashTable.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/dom/TouchEvent.h"
+#include "mozilla/dom/PointerEventBinding.h"
+#include "mozilla/dom/ShadowIncludingTreeIterator.h"
+#include "nsIObserverService.h"
+#include "nsDocShell.h" // for reflow observation
+#include "nsIBaseWindow.h"
+#include "nsError.h"
+#include "nsLayoutUtils.h"
+#include "nsViewportInfo.h"
+#include "nsCSSRendering.h"
+// for |#ifdef DEBUG| code
+#include "prenv.h"
+#include "nsDisplayList.h"
+#include "nsRegion.h"
+#include "nsAutoLayoutPhase.h"
+#include "AutoProfilerStyleMarker.h"
+#ifdef MOZ_REFLOW_PERF
+# include "nsFontMetrics.h"
+#endif
+#include "MobileViewportManager.h"
+#include "OverflowChangedTracker.h"
+#include "PositionedEventTargeting.h"
+
+#include "nsIReflowCallback.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsFocusManager.h"
+#include "nsNetUtil.h"
+#include "nsThreadUtils.h"
+#include "nsStyleSheetService.h"
+#include "gfxUtils.h"
+#include "mozilla/SMILAnimationController.h"
+#include "mozilla/dom/SVGAnimationElement.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGFragmentIdentifier.h"
+#include "nsFrameSelection.h"
+
+#include "mozilla/dom/Performance.h"
+#include "nsRefreshDriver.h"
+#include "nsDOMNavigationTiming.h"
+
+// Drag & Drop, Clipboard
+#include "nsIDocShellTreeItem.h"
+#include "nsIURI.h"
+#include "nsIScrollableFrame.h"
+#include "nsITimer.h"
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/DocAccessible.h"
+# ifdef DEBUG
+# include "mozilla/a11y/Logging.h"
+# endif
+#endif
+
+// For style data reconstruction
+#include "nsStyleChangeList.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsTreeBodyFrame.h"
+#include "XULTreeElement.h"
+#include "nsMenuPopupFrame.h"
+#include "nsTreeColumns.h"
+#include "nsIDOMXULMultSelectCntrlEl.h"
+#include "nsIDOMXULSelectCntrlItemEl.h"
+#include "nsIDOMXULMenuListElement.h"
+#include "nsXULElement.h"
+
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "gfxPlatform.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/DocumentTimeline.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "nsCanvasFrame.h"
+#include "nsImageFrame.h"
+#include "nsIScreen.h"
+#include "nsIScreenManager.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTransitionManager.h"
+#include "ChildIterator.h"
+#include "mozilla/RestyleManager.h"
+#include "nsIDragSession.h"
+#include "nsIFrameInlines.h"
+#include "mozilla/gfx/2D.h"
+#include "nsNetUtil.h"
+#include "nsSubDocumentFrame.h"
+#include "nsQueryObject.h"
+#include "mozilla/GlobalStyleSheetCache.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/FocusTarget.h"
+#include "mozilla/layers/ScrollingInteractionContext.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/WebRenderUserData.h"
+#include "mozilla/layout/ScrollAnchorContainer.h"
+#include "mozilla/layers/APZPublicUtils.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ScrollTimelineAnimationTracker.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/dom/ImageTracker.h"
+#include "nsIDocShellTreeOwner.h"
+#include "nsClassHashtable.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsHashKeys.h"
+#include "ScrollSnap.h"
+#include "VisualViewport.h"
+#include "ZoomConstraintsClient.h"
+
+// define the scalfactor of drag and drop images
+// relative to the max screen height/width
+#define RELATIVE_SCALEFACTOR 0.0925f
+
+using namespace mozilla;
+using namespace mozilla::css;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+using namespace mozilla::layout;
+using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
+typedef ScrollableLayerGuid::ViewID ViewID;
+
+PresShell::CapturingContentInfo PresShell::sCapturingContentInfo;
+
+// RangePaintInfo is used to paint ranges to offscreen buffers
+struct RangePaintInfo {
+ RefPtr<nsRange> mRange;
+ nsDisplayListBuilder mBuilder;
+ nsDisplayList mList;
+
+ // offset of builder's reference frame to the root frame
+ nsPoint mRootOffset;
+
+ // Resolution at which the items are normally painted. So if we're painting
+ // these items in a range separately from the "full display list", we may want
+ // to paint them at this resolution.
+ float mResolution = 1.0;
+
+ RangePaintInfo(nsRange* aRange, nsIFrame* aFrame)
+ : mRange(aRange),
+ mBuilder(aFrame, nsDisplayListBuilderMode::Painting, false),
+ mList(&mBuilder) {
+ MOZ_COUNT_CTOR(RangePaintInfo);
+ mBuilder.BeginFrame();
+ }
+
+ ~RangePaintInfo() {
+ mList.DeleteAll(&mBuilder);
+ mBuilder.EndFrame();
+ MOZ_COUNT_DTOR(RangePaintInfo);
+ }
+};
+
+#undef NOISY
+
+// ----------------------------------------------------------------------
+
+#ifdef DEBUG
+// Set the environment variable GECKO_VERIFY_REFLOW_FLAGS to one or
+// more of the following flags (comma separated) for handy debug
+// output.
+static VerifyReflowFlags gVerifyReflowFlags;
+
+struct VerifyReflowFlagData {
+ const char* name;
+ VerifyReflowFlags bit;
+};
+
+static const VerifyReflowFlagData gFlags[] = {
+ // clang-format off
+ { "verify", VerifyReflowFlags::On },
+ { "reflow", VerifyReflowFlags::Noisy },
+ { "all", VerifyReflowFlags::All },
+ { "list-commands", VerifyReflowFlags::DumpCommands },
+ { "noisy-commands", VerifyReflowFlags::NoisyCommands },
+ { "really-noisy-commands", VerifyReflowFlags::ReallyNoisyCommands },
+ { "resize", VerifyReflowFlags::DuringResizeReflow },
+ // clang-format on
+};
+
+# define NUM_VERIFY_REFLOW_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
+
+static void ShowVerifyReflowFlags() {
+ printf("Here are the available GECKO_VERIFY_REFLOW_FLAGS:\n");
+ const VerifyReflowFlagData* flag = gFlags;
+ const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
+ while (flag < limit) {
+ printf(" %s\n", flag->name);
+ ++flag;
+ }
+ printf("Note: GECKO_VERIFY_REFLOW_FLAGS is a comma separated list of flag\n");
+ printf("names (no whitespace)\n");
+}
+#endif
+
+//========================================================================
+//========================================================================
+//========================================================================
+#ifdef MOZ_REFLOW_PERF
+class ReflowCountMgr;
+
+static const char kGrandTotalsStr[] = "Grand Totals";
+
+// Counting Class
+class ReflowCounter {
+ public:
+ explicit ReflowCounter(ReflowCountMgr* aMgr = nullptr);
+ ~ReflowCounter();
+
+ void ClearTotals();
+ void DisplayTotals(const char* aStr);
+ void DisplayDiffTotals(const char* aStr);
+ void DisplayHTMLTotals(const char* aStr);
+
+ void Add() { mTotal++; }
+ void Add(uint32_t aTotal) { mTotal += aTotal; }
+
+ void CalcDiffInTotals();
+ void SetTotalsCache();
+
+ void SetMgr(ReflowCountMgr* aMgr) { mMgr = aMgr; }
+
+ uint32_t GetTotal() { return mTotal; }
+
+ protected:
+ void DisplayTotals(uint32_t aTotal, const char* aTitle);
+ void DisplayHTMLTotals(uint32_t aTotal, const char* aTitle);
+
+ uint32_t mTotal;
+ uint32_t mCacheTotal;
+
+ ReflowCountMgr* mMgr; // weak reference (don't delete)
+};
+
+// Counting Class
+class IndiReflowCounter {
+ public:
+ explicit IndiReflowCounter(ReflowCountMgr* aMgr = nullptr)
+ : mFrame(nullptr),
+ mCount(0),
+ mMgr(aMgr),
+ mCounter(aMgr),
+ mHasBeenOutput(false) {}
+ virtual ~IndiReflowCounter() = default;
+
+ nsAutoString mName;
+ nsIFrame* mFrame; // weak reference (don't delete)
+ int32_t mCount;
+
+ ReflowCountMgr* mMgr; // weak reference (don't delete)
+
+ ReflowCounter mCounter;
+ bool mHasBeenOutput;
+};
+
+//--------------------
+// Manager Class
+//--------------------
+class ReflowCountMgr {
+ public:
+ ReflowCountMgr();
+ virtual ~ReflowCountMgr();
+
+ void ClearTotals();
+ void ClearGrandTotals();
+ void DisplayTotals(const char* aStr);
+ void DisplayHTMLTotals(const char* aStr);
+ void DisplayDiffsInTotals();
+
+ void Add(const char* aName, nsIFrame* aFrame);
+ ReflowCounter* LookUp(const char* aName);
+
+ void PaintCount(const char* aName, gfxContext* aRenderingContext,
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ const nsPoint& aOffset, uint32_t aColor);
+
+ FILE* GetOutFile() { return mFD; }
+
+ void SetPresContext(nsPresContext* aPresContext) {
+ mPresContext = aPresContext; // weak reference
+ }
+ void SetPresShell(PresShell* aPresShell) {
+ mPresShell = aPresShell; // weak reference
+ }
+
+ void SetDumpFrameCounts(bool aVal) { mDumpFrameCounts = aVal; }
+ void SetDumpFrameByFrameCounts(bool aVal) { mDumpFrameByFrameCounts = aVal; }
+ void SetPaintFrameCounts(bool aVal) { mPaintFrameByFrameCounts = aVal; }
+
+ bool IsPaintingFrameCounts() { return mPaintFrameByFrameCounts; }
+
+ protected:
+ void DisplayTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
+ void DisplayHTMLTotals(uint32_t aTotal, uint32_t* aDupArray, char* aTitle);
+
+ void DoGrandTotals();
+ void DoIndiTotalsTree();
+
+ // HTML Output Methods
+ void DoGrandHTMLTotals();
+
+ nsClassHashtable<nsCharPtrHashKey, ReflowCounter> mCounts;
+ nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter> mIndiFrameCounts;
+ FILE* mFD;
+
+ bool mDumpFrameCounts;
+ bool mDumpFrameByFrameCounts;
+ bool mPaintFrameByFrameCounts;
+
+ bool mCycledOnce;
+
+ // Root Frame for Individual Tracking
+ nsPresContext* mPresContext;
+ PresShell* mPresShell;
+
+ // ReflowCountMgr gReflowCountMgr;
+};
+#endif
+//========================================================================
+
+// comment out to hide caret
+#define SHOW_CARET
+
+// The upper bound on the amount of time to spend reflowing, in
+// microseconds. When this bound is exceeded and reflow commands are
+// still queued up, a reflow event is posted. The idea is for reflow
+// to not hog the processor beyond the time specifed in
+// gMaxRCProcessingTime. This data member is initialized from the
+// layout.reflow.timeslice pref.
+#define NS_MAX_REFLOW_TIME 1000000
+static int32_t gMaxRCProcessingTime = -1;
+
+struct nsCallbackEventRequest {
+ nsIReflowCallback* callback;
+ nsCallbackEventRequest* next;
+};
+
+// ----------------------------------------------------------------------------
+//
+// NOTE(emilio): It'd be nice for this to assert that our document isn't in the
+// bfcache, but font pref changes don't care about that, and maybe / probably
+// shouldn't.
+#ifdef DEBUG
+# define ASSERT_REFLOW_SCHEDULED_STATE() \
+ { \
+ if (ObservingLayoutFlushes()) { \
+ MOZ_ASSERT( \
+ mDocument->GetBFCacheEntry() || \
+ mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
+ "Unexpected state"); \
+ } else { \
+ MOZ_ASSERT( \
+ !mPresContext->RefreshDriver()->IsLayoutFlushObserver(this), \
+ "Unexpected state"); \
+ } \
+ }
+#else
+# define ASSERT_REFLOW_SCHEDULED_STATE() /* nothing */
+#endif
+
+class nsAutoCauseReflowNotifier {
+ public:
+ MOZ_CAN_RUN_SCRIPT explicit nsAutoCauseReflowNotifier(PresShell* aPresShell)
+ : mPresShell(aPresShell) {
+ mPresShell->WillCauseReflow();
+ }
+ MOZ_CAN_RUN_SCRIPT ~nsAutoCauseReflowNotifier() {
+ // This check should not be needed. Currently the only place that seem
+ // to need it is the code that deals with bug 337586.
+ if (!mPresShell->mHaveShutDown) {
+ RefPtr<PresShell> presShell(mPresShell);
+ presShell->DidCauseReflow();
+ } else {
+ nsContentUtils::RemoveScriptBlocker();
+ }
+ }
+
+ PresShell* mPresShell;
+};
+
+class MOZ_STACK_CLASS nsPresShellEventCB : public EventDispatchingCallback {
+ public:
+ explicit nsPresShellEventCB(PresShell* aPresShell) : mPresShell(aPresShell) {}
+
+ MOZ_CAN_RUN_SCRIPT
+ virtual void HandleEvent(EventChainPostVisitor& aVisitor) override {
+ if (aVisitor.mPresContext && aVisitor.mEvent->mClass != eBasicEventClass) {
+ if (aVisitor.mEvent->mMessage == eMouseDown ||
+ aVisitor.mEvent->mMessage == eMouseUp) {
+ // Mouse-up and mouse-down events call nsIFrame::HandlePress/Release
+ // which call GetContentOffsetsFromPoint which requires up-to-date
+ // layout. Bring layout up-to-date now so that GetCurrentEventFrame()
+ // below will return a real frame and we don't have to worry about
+ // destroying it by flushing later.
+ MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
+ } else if (aVisitor.mEvent->mMessage == eWheel &&
+ aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
+ nsIFrame* frame = mPresShell->GetCurrentEventFrame();
+ if (frame) {
+ // chrome (including addons) should be able to know if content
+ // handles both D3E "wheel" event and legacy mouse scroll events.
+ // We should dispatch legacy mouse events before dispatching the
+ // "wheel" event into system group.
+ RefPtr<EventStateManager> esm =
+ aVisitor.mPresContext->EventStateManager();
+ esm->DispatchLegacyMouseScrollEvents(
+ frame, aVisitor.mEvent->AsWheelEvent(), &aVisitor.mEventStatus);
+ }
+ }
+ nsIFrame* frame = mPresShell->GetCurrentEventFrame();
+ if (!frame && (aVisitor.mEvent->mMessage == eMouseUp ||
+ aVisitor.mEvent->mMessage == eTouchEnd)) {
+ // Redirect BUTTON_UP and TOUCH_END events to the root frame to ensure
+ // that capturing is released.
+ frame = mPresShell->GetRootFrame();
+ }
+ if (frame) {
+ frame->HandleEvent(aVisitor.mPresContext, aVisitor.mEvent->AsGUIEvent(),
+ &aVisitor.mEventStatus);
+ }
+ }
+ }
+
+ RefPtr<PresShell> mPresShell;
+};
+
+class nsBeforeFirstPaintDispatcher : public Runnable {
+ public:
+ explicit nsBeforeFirstPaintDispatcher(Document* aDocument)
+ : mozilla::Runnable("nsBeforeFirstPaintDispatcher"),
+ mDocument(aDocument) {}
+
+ // Fires the "before-first-paint" event so that interested parties (right now,
+ // the mobile browser) are aware of it.
+ NS_IMETHOD Run() override {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(mDocument),
+ "before-first-paint", nullptr);
+ }
+ return NS_OK;
+ }
+
+ private:
+ RefPtr<Document> mDocument;
+};
+
+// This is a helper class to track whether the targeted frame is destroyed after
+// dispatching pointer events. In that case, we need the original targeted
+// content so that we can dispatch the mouse events to it.
+class MOZ_STACK_CLASS AutoPointerEventTargetUpdater final {
+ public:
+ AutoPointerEventTargetUpdater(PresShell* aShell, WidgetEvent* aEvent,
+ nsIFrame* aFrame, nsIContent** aTargetContent) {
+ MOZ_ASSERT(aEvent);
+ if (!aTargetContent || aEvent->mClass != ePointerEventClass) {
+ // Make the destructor happy.
+ mTargetContent = nullptr;
+ return;
+ }
+ MOZ_ASSERT(aShell);
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(!aFrame->GetContent() ||
+ aShell->GetDocument() == aFrame->GetContent()->OwnerDoc());
+
+ mShell = aShell;
+ mWeakFrame = aFrame;
+ mTargetContent = aTargetContent;
+ aShell->mPointerEventTarget = aFrame->GetContent();
+ }
+
+ ~AutoPointerEventTargetUpdater() {
+ if (!mTargetContent || !mShell || mWeakFrame.IsAlive()) {
+ return;
+ }
+ mShell->mPointerEventTarget.swap(*mTargetContent);
+ }
+
+ private:
+ RefPtr<PresShell> mShell;
+ AutoWeakFrame mWeakFrame;
+ nsIContent** mTargetContent;
+};
+
+bool PresShell::sDisableNonTestMouseEvents = false;
+int16_t PresShell::sMouseButtons = MouseButtonsFlag::eNoButtons;
+
+LazyLogModule PresShell::gLog("PresShell");
+
+TimeStamp PresShell::EventHandler::sLastInputCreated;
+TimeStamp PresShell::EventHandler::sLastInputProcessed;
+StaticRefPtr<Element> PresShell::EventHandler::sLastKeyDownEventTargetElement;
+
+bool PresShell::sProcessInteractable = false;
+
+static bool gVerifyReflowEnabled;
+
+bool PresShell::GetVerifyReflowEnable() {
+#ifdef DEBUG
+ static bool firstTime = true;
+ if (firstTime) {
+ firstTime = false;
+ char* flags = PR_GetEnv("GECKO_VERIFY_REFLOW_FLAGS");
+ if (flags) {
+ bool error = false;
+
+ for (;;) {
+ char* comma = strchr(flags, ',');
+ if (comma) *comma = '\0';
+
+ bool found = false;
+ const VerifyReflowFlagData* flag = gFlags;
+ const VerifyReflowFlagData* limit = gFlags + NUM_VERIFY_REFLOW_FLAGS;
+ while (flag < limit) {
+ if (nsCRT::strcasecmp(flag->name, flags) == 0) {
+ gVerifyReflowFlags |= flag->bit;
+ found = true;
+ break;
+ }
+ ++flag;
+ }
+
+ if (!found) error = true;
+
+ if (!comma) break;
+
+ *comma = ',';
+ flags = comma + 1;
+ }
+
+ if (error) ShowVerifyReflowFlags();
+ }
+
+ if (VerifyReflowFlags::On & gVerifyReflowFlags) {
+ gVerifyReflowEnabled = true;
+
+ printf("Note: verifyreflow is enabled");
+ if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
+ printf(" (noisy)");
+ }
+ if (VerifyReflowFlags::All & gVerifyReflowFlags) {
+ printf(" (all)");
+ }
+ if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
+ printf(" (show reflow commands)");
+ }
+ if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
+ printf(" (noisy reflow commands)");
+ if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
+ printf(" (REALLY noisy reflow commands)");
+ }
+ }
+ printf("\n");
+ }
+ }
+#endif
+ return gVerifyReflowEnabled;
+}
+
+void PresShell::SetVerifyReflowEnable(bool aEnabled) {
+ gVerifyReflowEnabled = aEnabled;
+}
+
+void PresShell::AddAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
+ if (aWeakFrame->GetFrame()) {
+ aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
+ }
+ aWeakFrame->SetPreviousWeakFrame(mAutoWeakFrames);
+ mAutoWeakFrames = aWeakFrame;
+}
+
+void PresShell::AddWeakFrame(WeakFrame* aWeakFrame) {
+ if (aWeakFrame->GetFrame()) {
+ aWeakFrame->GetFrame()->AddStateBits(NS_FRAME_EXTERNAL_REFERENCE);
+ }
+ MOZ_ASSERT(!mWeakFrames.Contains(aWeakFrame));
+ mWeakFrames.Insert(aWeakFrame);
+}
+
+void PresShell::RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame) {
+ if (mAutoWeakFrames == aWeakFrame) {
+ mAutoWeakFrames = aWeakFrame->GetPreviousWeakFrame();
+ return;
+ }
+ AutoWeakFrame* nextWeak = mAutoWeakFrames;
+ while (nextWeak && nextWeak->GetPreviousWeakFrame() != aWeakFrame) {
+ nextWeak = nextWeak->GetPreviousWeakFrame();
+ }
+ if (nextWeak) {
+ nextWeak->SetPreviousWeakFrame(aWeakFrame->GetPreviousWeakFrame());
+ }
+}
+
+void PresShell::RemoveWeakFrame(WeakFrame* aWeakFrame) {
+ MOZ_ASSERT(mWeakFrames.Contains(aWeakFrame));
+ mWeakFrames.Remove(aWeakFrame);
+}
+
+already_AddRefed<nsFrameSelection> PresShell::FrameSelection() {
+ RefPtr<nsFrameSelection> ret = mSelection;
+ return ret.forget();
+}
+
+//----------------------------------------------------------------------
+
+static uint32_t sNextPresShellId = 0;
+
+/* static */
+bool PresShell::AccessibleCaretEnabled(nsIDocShell* aDocShell) {
+ // If the pref forces it on, then enable it.
+ if (StaticPrefs::layout_accessiblecaret_enabled()) {
+ return true;
+ }
+ // If the touch pref is on, and touch events are enabled (this depends
+ // on the specific device running), then enable it.
+ if (StaticPrefs::layout_accessiblecaret_enabled_on_touch() &&
+ dom::TouchEvent::PrefEnabled(aDocShell)) {
+ return true;
+ }
+ // Otherwise, disabled.
+ return false;
+}
+
+PresShell::PresShell(Document* aDocument)
+ : mDocument(aDocument),
+ mViewManager(nullptr),
+ mFrameManager(nullptr),
+ mAutoWeakFrames(nullptr),
+#ifdef ACCESSIBILITY
+ mDocAccessible(nullptr),
+#endif // ACCESSIBILITY
+ mCurrentEventFrame(nullptr),
+ mMouseLocation(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE),
+ mLastResolutionChangeOrigin(ResolutionChangeOrigin::Apz),
+ mPaintCount(0),
+ mAPZFocusSequenceNumber(0),
+ mActiveSuppressDisplayport(0),
+ mPresShellId(++sNextPresShellId),
+ mFontSizeInflationEmPerLine(0),
+ mFontSizeInflationMinTwips(0),
+ mFontSizeInflationLineThreshold(0),
+ mSelectionFlags(nsISelectionDisplay::DISPLAY_TEXT |
+ nsISelectionDisplay::DISPLAY_IMAGES),
+ mChangeNestCount(0),
+ mRenderingStateFlags(RenderingStateFlags::None),
+ mInFlush(false),
+ mCaretEnabled(false),
+ mNeedLayoutFlush(true),
+ mNeedStyleFlush(true),
+ mNeedThrottledAnimationFlush(true),
+ mVisualViewportSizeSet(false),
+ mDidInitialize(false),
+ mIsDestroying(false),
+ mIsReflowing(false),
+ mIsObservingDocument(false),
+ mForbiddenToFlush(false),
+ mIsDocumentGone(false),
+ mHaveShutDown(false),
+ mPaintingSuppressed(false),
+ mLastRootReflowHadUnconstrainedBSize(false),
+ mShouldUnsuppressPainting(false),
+ mIgnoreFrameDestruction(false),
+ mIsActive(true),
+ mFrozen(false),
+ mIsFirstPaint(true),
+ mObservesMutationsForPrint(false),
+ mWasLastReflowInterrupted(false),
+ mObservingStyleFlushes(false),
+ mObservingLayoutFlushes(false),
+ mResizeEventPending(false),
+ mFontSizeInflationForceEnabled(false),
+ mFontSizeInflationDisabledInMasterProcess(false),
+ mFontSizeInflationEnabled(false),
+ mIsNeverPainting(false),
+ mResolutionUpdated(false),
+ mResolutionUpdatedByApz(false),
+ mUnderHiddenEmbedderElement(false),
+ mDocumentLoading(false),
+ mNoDelayedMouseEvents(false),
+ mNoDelayedKeyEvents(false),
+ mApproximateFrameVisibilityVisited(false),
+ mIsLastChromeOnlyEscapeKeyConsumed(false),
+ mHasReceivedPaintMessage(false),
+ mIsLastKeyDownCanceled(false),
+ mHasHandledUserInput(false),
+ mForceDispatchKeyPressEventsForNonPrintableKeys(false),
+ mForceUseLegacyKeyCodeAndCharCodeValues(false),
+ mInitializedWithKeyPressEventDispatchingBlacklist(false),
+ mMouseLocationWasSetBySynthesizedMouseEventForTests(false),
+ mHasTriedFastUnsuppress(false),
+ mProcessingReflowCommands(false),
+ mPendingDidDoReflow(false) {
+ MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::PresShell this=%p", this));
+ MOZ_ASSERT(aDocument);
+
+#ifdef MOZ_REFLOW_PERF
+ mReflowCountMgr = MakeUnique<ReflowCountMgr>();
+ mReflowCountMgr->SetPresContext(mPresContext);
+ mReflowCountMgr->SetPresShell(this);
+#endif
+ mLastOSWake = mLoadBegin = TimeStamp::Now();
+}
+
+NS_INTERFACE_TABLE_HEAD(PresShell)
+ NS_INTERFACE_TABLE_BEGIN
+ // In most cases, PresShell should be treated as concrete class, but need to
+ // QI for weak reference. Therefore, the case needed by do_QueryReferent()
+ // should be tested first.
+ NS_INTERFACE_TABLE_ENTRY(PresShell, PresShell)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsIDocumentObserver)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionController)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsISelectionDisplay)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsIObserver)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsISupportsWeakReference)
+ NS_INTERFACE_TABLE_ENTRY(PresShell, nsIMutationObserver)
+ NS_INTERFACE_TABLE_ENTRY_AMBIGUOUS(PresShell, nsISupports, nsIObserver)
+ NS_INTERFACE_TABLE_END
+ NS_INTERFACE_TABLE_TO_MAP_SEGUE
+NS_INTERFACE_MAP_END
+
+NS_IMPL_ADDREF(PresShell)
+NS_IMPL_RELEASE(PresShell)
+
+PresShell::~PresShell() {
+ MOZ_RELEASE_ASSERT(!mForbiddenToFlush,
+ "Flag should only be set temporarily, while doing things "
+ "that shouldn't cause destruction");
+ MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::~PresShell this=%p", this));
+
+ if (!mHaveShutDown) {
+ MOZ_ASSERT_UNREACHABLE("Someone did not call PresShell::Destroy()");
+ Destroy();
+ }
+
+ NS_ASSERTION(mCurrentEventContentStack.Count() == 0,
+ "Huh, event content left on the stack in pres shell dtor!");
+ NS_ASSERTION(mFirstCallbackEventRequest == nullptr &&
+ mLastCallbackEventRequest == nullptr,
+ "post-reflow queues not empty. This means we're leaking");
+
+ MOZ_ASSERT(!mAllocatedPointers || mAllocatedPointers->IsEmpty(),
+ "Some pres arena objects were not freed");
+
+ mFrameManager = nullptr;
+ mFrameConstructor = nullptr;
+
+ mCurrentEventContent = nullptr;
+}
+
+/**
+ * Initialize the presentation shell. Create view manager and style
+ * manager.
+ * Note this can't be merged into our constructor because caret initialization
+ * calls AddRef() on us.
+ */
+void PresShell::Init(nsPresContext* aPresContext, nsViewManager* aViewManager) {
+ MOZ_ASSERT(mDocument);
+ MOZ_ASSERT(aPresContext);
+ MOZ_ASSERT(aViewManager);
+ MOZ_ASSERT(!mViewManager, "already initialized");
+
+ mViewManager = aViewManager;
+
+ // mDocument is now set. It might have a display document whose "need layout/
+ // style" flush flags are not set, but ours will be set. To keep these
+ // consistent, call the flag setting functions to propagate those flags up
+ // to the display document.
+ SetNeedLayoutFlush();
+ SetNeedStyleFlush();
+
+ // Create our frame constructor.
+ mFrameConstructor = MakeUnique<nsCSSFrameConstructor>(mDocument, this);
+
+ mFrameManager = mFrameConstructor.get();
+
+ // The document viewer owns both view manager and pres shell.
+ mViewManager->SetPresShell(this);
+
+ // Bind the context to the presentation shell.
+ // FYI: We cannot initialize mPresContext in the constructor because we
+ // cannot call AttachPresShell() in it and once we initialize
+ // mPresContext, other objects may refer refresh driver or restyle
+ // manager via mPresContext and that causes hitting MOZ_ASSERT in some
+ // places. Therefore, we should initialize mPresContext here with
+ // const_cast hack since we want to guarantee that mPresContext lives
+ // as long as the PresShell.
+ const_cast<RefPtr<nsPresContext>&>(mPresContext) = aPresContext;
+ mPresContext->AttachPresShell(this);
+
+ mPresContext->InitFontCache();
+
+ // FIXME(emilio, bug 1544185): Some Android code somehow depends on the shell
+ // being eagerly registered as a style flush observer. This shouldn't be
+ // needed otherwise.
+ EnsureStyleFlush();
+
+ const bool accessibleCaretEnabled =
+ AccessibleCaretEnabled(mDocument->GetDocShell());
+ if (accessibleCaretEnabled) {
+ // Need to happen before nsFrameSelection has been set up.
+ mAccessibleCaretEventHub = new AccessibleCaretEventHub(this);
+ mAccessibleCaretEventHub->Init();
+ }
+
+ mSelection = new nsFrameSelection(this, nullptr, accessibleCaretEnabled);
+
+ // Important: this has to happen after the selection has been set up
+#ifdef SHOW_CARET
+ // make the caret
+ mCaret = new nsCaret();
+ mCaret->Init(this);
+ mOriginalCaret = mCaret;
+
+ // SetCaretEnabled(true); // make it show in browser windows
+#endif
+ // set up selection to be displayed in document
+ // Don't enable selection for print media
+ nsPresContext::nsPresContextType type = mPresContext->Type();
+ if (type != nsPresContext::eContext_PrintPreview &&
+ type != nsPresContext::eContext_Print) {
+ SetDisplaySelection(nsISelectionController::SELECTION_DISABLED);
+ }
+
+ if (gMaxRCProcessingTime == -1) {
+ gMaxRCProcessingTime =
+ Preferences::GetInt("layout.reflow.timeslice", NS_MAX_REFLOW_TIME);
+ }
+
+ if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
+ ss->RegisterPresShell(this);
+ }
+
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "memory-pressure", false);
+ os->AddObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC, false);
+ if (XRE_IsParentProcess() && !sProcessInteractable) {
+ os->AddObserver(this, "sessionstore-one-or-no-tab-restored", false);
+ }
+ os->AddObserver(this, "font-info-updated", false);
+ os->AddObserver(this, "internal-look-and-feel-changed", false);
+ }
+ }
+
+#ifdef MOZ_REFLOW_PERF
+ if (mReflowCountMgr) {
+ bool paintFrameCounts =
+ Preferences::GetBool("layout.reflow.showframecounts");
+
+ bool dumpFrameCounts =
+ Preferences::GetBool("layout.reflow.dumpframecounts");
+
+ bool dumpFrameByFrameCounts =
+ Preferences::GetBool("layout.reflow.dumpframebyframecounts");
+
+ mReflowCountMgr->SetDumpFrameCounts(dumpFrameCounts);
+ mReflowCountMgr->SetDumpFrameByFrameCounts(dumpFrameByFrameCounts);
+ mReflowCountMgr->SetPaintFrameCounts(paintFrameCounts);
+ }
+#endif
+
+ if (mDocument->HasAnimationController()) {
+ SMILAnimationController* animCtrl = mDocument->GetAnimationController();
+ animCtrl->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
+ }
+
+ for (DocumentTimeline* timeline : mDocument->Timelines()) {
+ timeline->NotifyRefreshDriverCreated(GetPresContext()->RefreshDriver());
+ }
+
+ // Get our activeness from the docShell.
+ ActivenessMaybeChanged();
+
+ // Setup our font inflation preferences.
+ mFontSizeInflationEmPerLine = StaticPrefs::font_size_inflation_emPerLine();
+ mFontSizeInflationMinTwips = StaticPrefs::font_size_inflation_minTwips();
+ mFontSizeInflationLineThreshold =
+ StaticPrefs::font_size_inflation_lineThreshold();
+ mFontSizeInflationForceEnabled =
+ StaticPrefs::font_size_inflation_forceEnabled();
+ mFontSizeInflationDisabledInMasterProcess =
+ StaticPrefs::font_size_inflation_disabledInMasterProcess();
+ // We'll compute the font size inflation state in Initialize(), when we know
+ // the document type.
+
+ mTouchManager.Init(this, mDocument);
+
+ if (mPresContext->IsRootContentDocumentCrossProcess()) {
+ mZoomConstraintsClient = new ZoomConstraintsClient();
+ mZoomConstraintsClient->Init(this, mDocument);
+
+ // We call this to create mMobileViewportManager, if it is needed.
+ MaybeRecreateMobileViewportManager(false);
+ }
+
+ if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
+ if (BrowsingContext* bc = docShell->GetBrowsingContext()) {
+ mUnderHiddenEmbedderElement = bc->IsUnderHiddenEmbedderElement();
+ }
+ }
+}
+
+enum TextPerfLogType { eLog_reflow, eLog_loaddone, eLog_totals };
+
+static void LogTextPerfStats(gfxTextPerfMetrics* aTextPerf,
+ PresShell* aPresShell,
+ const gfxTextPerfMetrics::TextCounts& aCounts,
+ float aTime, TextPerfLogType aLogType,
+ const char* aURL) {
+ LogModule* tpLog = gfxPlatform::GetLog(eGfxLog_textperf);
+
+ // ignore XUL contexts unless at debug level
+ mozilla::LogLevel logLevel = LogLevel::Warning;
+ if (aCounts.numContentTextRuns == 0) {
+ logLevel = LogLevel::Debug;
+ }
+
+ if (!MOZ_LOG_TEST(tpLog, logLevel)) {
+ return;
+ }
+
+ char prefix[256];
+
+ switch (aLogType) {
+ case eLog_reflow:
+ SprintfLiteral(prefix, "(textperf-reflow) %p time-ms: %7.0f", aPresShell,
+ aTime);
+ break;
+ case eLog_loaddone:
+ SprintfLiteral(prefix, "(textperf-loaddone) %p time-ms: %7.0f",
+ aPresShell, aTime);
+ break;
+ default:
+ MOZ_ASSERT(aLogType == eLog_totals, "unknown textperf log type");
+ SprintfLiteral(prefix, "(textperf-totals) %p", aPresShell);
+ }
+
+ double hitRatio = 0.0;
+ uint32_t lookups = aCounts.wordCacheHit + aCounts.wordCacheMiss;
+ if (lookups) {
+ hitRatio = double(aCounts.wordCacheHit) / double(lookups);
+ }
+
+ if (aLogType == eLog_loaddone) {
+ MOZ_LOG(
+ tpLog, logLevel,
+ ("%s reflow: %d chars: %d "
+ "[%s] "
+ "content-textruns: %d chrome-textruns: %d "
+ "max-textrun-len: %d "
+ "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
+ "word-cache-space: %d word-cache-long: %d "
+ "pref-fallbacks: %d system-fallbacks: %d "
+ "textruns-const: %d textruns-destr: %d "
+ "generic-lookups: %d "
+ "cumulative-textruns-destr: %d\n",
+ prefix, aTextPerf->reflowCount, aCounts.numChars, (aURL ? aURL : ""),
+ aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
+ aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
+ aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
+ aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
+ aTextPerf->cumulative.textrunDestr));
+ } else {
+ MOZ_LOG(
+ tpLog, logLevel,
+ ("%s reflow: %d chars: %d "
+ "content-textruns: %d chrome-textruns: %d "
+ "max-textrun-len: %d "
+ "word-cache-lookups: %d word-cache-hit-ratio: %4.3f "
+ "word-cache-space: %d word-cache-long: %d "
+ "pref-fallbacks: %d system-fallbacks: %d "
+ "textruns-const: %d textruns-destr: %d "
+ "generic-lookups: %d "
+ "cumulative-textruns-destr: %d\n",
+ prefix, aTextPerf->reflowCount, aCounts.numChars,
+ aCounts.numContentTextRuns, aCounts.numChromeTextRuns,
+ aCounts.maxTextRunLen, lookups, hitRatio, aCounts.wordCacheSpaceRules,
+ aCounts.wordCacheLong, aCounts.fallbackPrefs, aCounts.fallbackSystem,
+ aCounts.textrunConst, aCounts.textrunDestr, aCounts.genericLookups,
+ aTextPerf->cumulative.textrunDestr));
+ }
+}
+
+bool PresShell::InRDMPane() {
+ if (Document* doc = GetDocument()) {
+ if (BrowsingContext* bc = doc->GetBrowsingContext()) {
+ return bc->InRDMPane();
+ }
+ }
+ return false;
+}
+
+#if defined(MOZ_WIDGET_ANDROID)
+void PresShell::MaybeNotifyShowDynamicToolbar() {
+ const DynamicToolbarState dynToolbarState = GetDynamicToolbarState();
+ if ((dynToolbarState == DynamicToolbarState::Collapsed ||
+ dynToolbarState == DynamicToolbarState::InTransition)) {
+ MOZ_ASSERT(mPresContext &&
+ mPresContext->IsRootContentDocumentCrossProcess());
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
+ browserChild->SendShowDynamicToolbar();
+ }
+ }
+}
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+void PresShell::Destroy() {
+ // Do not add code before this line please!
+ if (mHaveShutDown) {
+ return;
+ }
+
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "destroy called on presshell while scripts not blocked");
+
+ [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
+ "Layout tree destruction", LAYOUT_Destroy,
+ uri ? uri->GetSpecOrDefault() : "N/A"_ns);
+
+ // Try to determine if the page is the user had a meaningful opportunity to
+ // zoom this page. This is not 100% accurate but should be "good enough" for
+ // telemetry purposes.
+ auto isUserZoomablePage = [&]() -> bool {
+ if (mIsFirstPaint) {
+ // Page was never painted, so it wasn't zoomable by the user. We get a
+ // handful of these "transient" presShells.
+ return false;
+ }
+ if (!mPresContext->IsRootContentDocumentCrossProcess()) {
+ // Not a root content document, so APZ doesn't support zooming it.
+ return false;
+ }
+ if (InRDMPane()) {
+ // Responsive design mode is a special case that we want to ignore here.
+ return false;
+ }
+ if (mDocument && mDocument->IsInitialDocument()) {
+ // Ignore initial about:blank page loads
+ return false;
+ }
+ if (XRE_IsContentProcess() &&
+ IsExtensionRemoteType(ContentChild::GetSingleton()->GetRemoteType())) {
+ // Also omit presShells from the extension process because they sometimes
+ // can't be zoomed by the user.
+ return false;
+ }
+ // Otherwise assume the page is user-zoomable.
+ return true;
+ };
+ if (isUserZoomablePage()) {
+ Telemetry::Accumulate(Telemetry::APZ_ZOOM_ACTIVITY,
+ IsResolutionUpdatedByApz());
+ }
+
+ // dump out cumulative text perf metrics
+ gfxTextPerfMetrics* tp;
+ if (mPresContext && (tp = mPresContext->GetTextPerfMetrics())) {
+ tp->Accumulate();
+ if (tp->cumulative.numChars > 0) {
+ LogTextPerfStats(tp, this, tp->cumulative, 0.0, eLog_totals, nullptr);
+ }
+ }
+ if (mPresContext) {
+ if (gfxUserFontSet* fs = mPresContext->GetUserFontSet()) {
+ uint32_t fontCount;
+ uint64_t fontSize;
+ fs->GetLoadStatistics(fontCount, fontSize);
+ Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, fontCount);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE,
+ uint32_t(fontSize / 1024));
+ } else {
+ Telemetry::Accumulate(Telemetry::WEBFONT_PER_PAGE, 0);
+ Telemetry::Accumulate(Telemetry::WEBFONT_SIZE_PER_PAGE, 0);
+ }
+ }
+
+#ifdef MOZ_REFLOW_PERF
+ DumpReflows();
+ mReflowCountMgr = nullptr;
+#endif
+
+ if (mZoomConstraintsClient) {
+ mZoomConstraintsClient->Destroy();
+ mZoomConstraintsClient = nullptr;
+ }
+ if (mMobileViewportManager) {
+ mMobileViewportManager->Destroy();
+ mMobileViewportManager = nullptr;
+ mMVMContext = nullptr;
+ }
+
+#ifdef ACCESSIBILITY
+ if (mDocAccessible) {
+# ifdef DEBUG
+ if (a11y::logging::IsEnabled(a11y::logging::eDocDestroy))
+ a11y::logging::DocDestroy("presshell destroyed", mDocument);
+# endif
+
+ mDocAccessible->Shutdown();
+ mDocAccessible = nullptr;
+ }
+#endif // ACCESSIBILITY
+
+ MaybeReleaseCapturingContent();
+
+ EventHandler::OnPresShellDestroy(mDocument);
+
+ if (mContentToScrollTo) {
+ mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ }
+
+ if (mPresContext) {
+ // We need to notify the destroying the nsPresContext to ESM for
+ // suppressing to use from ESM.
+ mPresContext->EventStateManager()->NotifyDestroyPresContext(mPresContext);
+ }
+
+ if (nsStyleSheetService* ss = nsStyleSheetService::GetInstance()) {
+ ss->UnregisterPresShell(this);
+ }
+
+ {
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, "memory-pressure");
+ os->RemoveObserver(this, NS_WIDGET_WAKE_OBSERVER_TOPIC);
+ if (XRE_IsParentProcess()) {
+ os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
+ }
+ os->RemoveObserver(this, "font-info-updated");
+ os->RemoveObserver(this, "internal-look-and-feel-changed");
+ }
+ }
+
+ // If our paint suppression timer is still active, kill it.
+ CancelPaintSuppressionTimer();
+
+ // Same for our reflow continuation timer
+ if (mReflowContinueTimer) {
+ mReflowContinueTimer->Cancel();
+ mReflowContinueTimer = nullptr;
+ }
+
+ mSynthMouseMoveEvent.Revoke();
+
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
+
+ if (mCaret) {
+ mCaret->Terminate();
+ mCaret = nullptr;
+ }
+
+ mFocusedFrameSelection = nullptr;
+
+ if (mSelection) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->DisconnectFromPresShell();
+ }
+
+ mIsDestroying = true;
+
+ // We can't release all the event content in
+ // mCurrentEventContentStack here since there might be code on the
+ // stack that will release the event content too. Double release
+ // bad!
+
+ // The frames will be torn down, so remove them from the current
+ // event frame stack (since they'd be dangling references if we'd
+ // leave them in) and null out the mCurrentEventFrame pointer as
+ // well.
+
+ mCurrentEventFrame = nullptr;
+
+ int32_t i, count = mCurrentEventFrameStack.Length();
+ for (i = 0; i < count; i++) {
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+
+ mFramesToDirty.Clear();
+ mPendingScrollAnchorSelection.Clear();
+ mPendingScrollAnchorAdjustment.Clear();
+ mPendingScrollResnap.Clear();
+
+ if (mViewManager) {
+ // Clear the view manager's weak pointer back to |this| in case it
+ // was leaked.
+ mViewManager->SetPresShell(nullptr);
+ mViewManager = nullptr;
+ }
+
+ nsRefreshDriver* rd = GetPresContext()->RefreshDriver();
+
+ // This shell must be removed from the document before the frame
+ // hierarchy is torn down to avoid finding deleted frames through
+ // this presshell while the frames are being torn down
+ if (mDocument) {
+ NS_ASSERTION(mDocument->GetPresShell() == this, "Wrong shell?");
+ mDocument->ClearServoRestyleRoot();
+ mDocument->DeletePresShell();
+
+ if (mDocument->HasAnimationController()) {
+ mDocument->GetAnimationController()->NotifyRefreshDriverDestroying(rd);
+ }
+ for (DocumentTimeline* timeline : mDocument->Timelines()) {
+ timeline->NotifyRefreshDriverDestroying(rd);
+ }
+ }
+
+ if (mPresContext) {
+ rd->CancelPendingAnimationEvents(mPresContext->AnimationEventDispatcher());
+ }
+
+ // Revoke any pending events. We need to do this and cancel pending reflows
+ // before we destroy the frame manager, since apparently frame destruction
+ // sometimes spins the event queue when plug-ins are involved(!).
+ // XXXmats is this still needed now that plugins are gone?
+ StopObservingRefreshDriver();
+
+ if (rd->GetPresContext() == GetPresContext()) {
+ rd->RevokeViewManagerFlush();
+ rd->ClearHasScheduleFlush();
+ }
+
+ CancelAllPendingReflows();
+ CancelPostedReflowCallbacks();
+
+ // Destroy the frame manager. This will destroy the frame hierarchy
+ mFrameConstructor->WillDestroyFrameTree();
+
+ NS_WARNING_ASSERTION(!mAutoWeakFrames && mWeakFrames.IsEmpty(),
+ "Weak frames alive after destroying FrameManager");
+ while (mAutoWeakFrames) {
+ mAutoWeakFrames->Clear(this);
+ }
+ const nsTArray<WeakFrame*> weakFrames = ToArray(mWeakFrames);
+ for (WeakFrame* weakFrame : weakFrames) {
+ weakFrame->Clear(this);
+ }
+
+ // Terminate AccessibleCaretEventHub after tearing down the frame tree so that
+ // we don't need to remove caret element's frame in
+ // AccessibleCaret::RemoveCaretElement().
+ if (mAccessibleCaretEventHub) {
+ mAccessibleCaretEventHub->Terminate();
+ mAccessibleCaretEventHub = nullptr;
+ }
+
+ if (mPresContext) {
+ // We hold a reference to the pres context, and it holds a weak link back
+ // to us. To avoid the pres context having a dangling reference, set its
+ // pres shell to nullptr
+ mPresContext->DetachPresShell();
+ }
+
+ mHaveShutDown = true;
+
+ mTouchManager.Destroy();
+}
+
+void PresShell::StopObservingRefreshDriver() {
+ nsRefreshDriver* rd = mPresContext->RefreshDriver();
+ if (mResizeEventPending) {
+ rd->RemoveResizeEventFlushObserver(this);
+ }
+ if (mObservingLayoutFlushes) {
+ rd->RemoveLayoutFlushObserver(this);
+ }
+ if (mObservingStyleFlushes) {
+ rd->RemoveStyleFlushObserver(this);
+ }
+}
+
+void PresShell::StartObservingRefreshDriver() {
+ nsRefreshDriver* rd = mPresContext->RefreshDriver();
+ if (mResizeEventPending) {
+ rd->AddResizeEventFlushObserver(this);
+ }
+ if (mObservingLayoutFlushes) {
+ rd->AddLayoutFlushObserver(this);
+ }
+ if (mObservingStyleFlushes) {
+ rd->AddStyleFlushObserver(this);
+ }
+}
+
+nsRefreshDriver* PresShell::GetRefreshDriver() const {
+ return mPresContext ? mPresContext->RefreshDriver() : nullptr;
+}
+
+void PresShell::SetAuthorStyleDisabled(bool aStyleDisabled) {
+ if (aStyleDisabled != StyleSet()->GetAuthorStyleDisabled()) {
+ StyleSet()->SetAuthorStyleDisabled(aStyleDisabled);
+ mDocument->ApplicableStylesChanged();
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(
+ ToSupports(mDocument), "author-style-disabled-changed", nullptr);
+ }
+ }
+}
+
+bool PresShell::GetAuthorStyleDisabled() const {
+ return StyleSet()->GetAuthorStyleDisabled();
+}
+
+void PresShell::AddUserSheet(StyleSheet* aSheet) {
+ // Make sure this does what nsDocumentViewer::CreateStyleSet does wrt
+ // ordering. We want this new sheet to come after all the existing stylesheet
+ // service sheets (which are at the start), but before other user sheets; see
+ // nsIStyleSheetService.idl for the ordering.
+
+ nsStyleSheetService* sheetService = nsStyleSheetService::GetInstance();
+ nsTArray<RefPtr<StyleSheet>>& userSheets = *sheetService->UserStyleSheets();
+
+ // Search for the place to insert the new user sheet. Since all of the
+ // stylesheet service provided user sheets should be at the start of the style
+ // set's list, and aSheet should be at the end of userSheets. Given that, we
+ // can find the right place to insert the new sheet based on the length of
+ // userSheets.
+ MOZ_ASSERT(aSheet);
+ MOZ_ASSERT(userSheets.LastElement() == aSheet);
+
+ size_t index = userSheets.Length() - 1;
+
+ // Assert that all of userSheets (except for the last, new element) matches up
+ // with what's in the style set.
+ for (size_t i = 0; i < index; ++i) {
+ MOZ_ASSERT(StyleSet()->SheetAt(StyleOrigin::User, i) == userSheets[i]);
+ }
+
+ if (index == static_cast<size_t>(StyleSet()->SheetCount(StyleOrigin::User))) {
+ StyleSet()->AppendStyleSheet(*aSheet);
+ } else {
+ StyleSheet* ref = StyleSet()->SheetAt(StyleOrigin::User, index);
+ StyleSet()->InsertStyleSheetBefore(*aSheet, *ref);
+ }
+
+ mDocument->ApplicableStylesChanged();
+}
+
+void PresShell::AddAgentSheet(StyleSheet* aSheet) {
+ // Make sure this does what nsDocumentViewer::CreateStyleSet does
+ // wrt ordering.
+ StyleSet()->AppendStyleSheet(*aSheet);
+ mDocument->ApplicableStylesChanged();
+}
+
+void PresShell::AddAuthorSheet(StyleSheet* aSheet) {
+ // Document specific "additional" Author sheets should be stronger than the
+ // ones added with the StyleSheetService.
+ StyleSheet* firstAuthorSheet = mDocument->GetFirstAdditionalAuthorSheet();
+ if (firstAuthorSheet) {
+ StyleSet()->InsertStyleSheetBefore(*aSheet, *firstAuthorSheet);
+ } else {
+ StyleSet()->AppendStyleSheet(*aSheet);
+ }
+
+ mDocument->ApplicableStylesChanged();
+}
+
+bool PresShell::FixUpFocus() {
+ if (NS_WARN_IF(!mDocument)) {
+ return false;
+ }
+
+ nsIContent* currentFocus = mDocument->GetUnretargetedFocusedContent(
+ Document::IncludeChromeOnly::Yes);
+ if (!currentFocus) {
+ return false;
+ }
+
+ // If focus target is an area element with one or more shapes that are
+ // focusable areas.
+ if (auto* area = HTMLAreaElement::FromNode(currentFocus)) {
+ if (nsFocusManager::IsAreaElementFocusable(*area)) {
+ return false;
+ }
+ }
+
+ nsIFrame* f = currentFocus->GetPrimaryFrame();
+ if (f && f->IsFocusable()) {
+ return false;
+ }
+
+ if (currentFocus == mDocument->GetBody() ||
+ currentFocus == mDocument->GetRootElement()) {
+ return false;
+ }
+
+ RefPtr fm = nsFocusManager::GetFocusManager();
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (NS_WARN_IF(!window)) {
+ return false;
+ }
+ fm->ClearFocus(window);
+ return true;
+}
+
+void PresShell::SelectionWillTakeFocus() {
+ if (mSelection) {
+ FrameSelectionWillTakeFocus(*mSelection);
+ }
+}
+
+void PresShell::SelectionWillLoseFocus() {
+ // Do nothing, the main selection is the default focused selection.
+}
+
+// Selection repainting code relies on selection offsets being properly
+// adjusted (see bug 1626291), so we need to wait until the DOM is finished
+// notifying.
+static void RepaintNormalSelectionWhenSafe(nsFrameSelection& aFrameSelection) {
+ if (nsContentUtils::IsSafeToRunScript()) {
+ aFrameSelection.RepaintSelection(SelectionType::eNormal);
+ return;
+ }
+
+ // Note that importantly we don't defer changing the DisplaySelection. That'd
+ // be potentially racy with other code that may change it.
+ nsContentUtils::AddScriptRunner(NS_NewRunnableFunction(
+ "RepaintNormalSelectionWhenSafe",
+ [sel = RefPtr<nsFrameSelection>(&aFrameSelection)] {
+ sel->RepaintSelection(SelectionType::eNormal);
+ }));
+}
+
+void PresShell::FrameSelectionWillLoseFocus(nsFrameSelection& aFrameSelection) {
+ if (mFocusedFrameSelection != &aFrameSelection) {
+ return;
+ }
+
+ // Do nothing, the main selection is the default focused selection.
+ if (&aFrameSelection == mSelection) {
+ return;
+ }
+
+ RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
+ MOZ_ASSERT(!mFocusedFrameSelection);
+
+ if (old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
+ old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
+ RepaintNormalSelectionWhenSafe(*old);
+ }
+
+ if (mSelection) {
+ FrameSelectionWillTakeFocus(*mSelection);
+ }
+}
+
+void PresShell::FrameSelectionWillTakeFocus(nsFrameSelection& aFrameSelection) {
+ if (mFocusedFrameSelection == &aFrameSelection) {
+#ifdef XP_MACOSX
+ // FIXME: Mac needs to update the global selection cache, even if the
+ // document's focused selection doesn't change, and this is currently done
+ // from RepaintSelection. Maybe we should move part of the global selection
+ // handling here, or something of that sort, unclear.
+ RepaintNormalSelectionWhenSafe(aFrameSelection);
+#endif
+ return;
+ }
+
+ RefPtr<nsFrameSelection> old = std::move(mFocusedFrameSelection);
+ mFocusedFrameSelection = &aFrameSelection;
+
+ if (old &&
+ old->GetDisplaySelection() != nsISelectionController::SELECTION_HIDDEN) {
+ old->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
+ RepaintNormalSelectionWhenSafe(*old);
+ }
+
+ if (aFrameSelection.GetDisplaySelection() !=
+ nsISelectionController::SELECTION_ON) {
+ aFrameSelection.SetDisplaySelection(nsISelectionController::SELECTION_ON);
+ RepaintNormalSelectionWhenSafe(aFrameSelection);
+ }
+}
+
+NS_IMETHODIMP
+PresShell::SetDisplaySelection(int16_t aToggle) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ frameSelection->SetDisplaySelection(aToggle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::GetDisplaySelection(int16_t* aToggle) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ *aToggle = frameSelection->GetDisplaySelection();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::GetSelectionFromScript(RawSelectionType aRawSelectionType,
+ Selection** aSelection) {
+ if (!aSelection || !mSelection) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ RefPtr<Selection> selection =
+ frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
+
+ if (!selection) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ selection.forget(aSelection);
+ return NS_OK;
+}
+
+Selection* PresShell::GetSelection(RawSelectionType aRawSelectionType) {
+ if (!mSelection) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->GetSelection(ToSelectionType(aRawSelectionType));
+}
+
+Selection* PresShell::GetCurrentSelection(SelectionType aSelectionType) {
+ if (!mSelection) {
+ return nullptr;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->GetSelection(aSelectionType);
+}
+
+nsFrameSelection* PresShell::GetLastFocusedFrameSelection() {
+ return mFocusedFrameSelection ? mFocusedFrameSelection : mSelection;
+}
+
+NS_IMETHODIMP
+PresShell::ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
+ SelectionRegion aRegion, int16_t aFlags) {
+ if (!mSelection) return NS_ERROR_NULL_POINTER;
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->ScrollSelectionIntoView(
+ ToSelectionType(aRawSelectionType), aRegion, aFlags);
+}
+
+NS_IMETHODIMP
+PresShell::RepaintSelection(RawSelectionType aRawSelectionType) {
+ if (!mSelection) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return NS_OK;
+ }
+
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->RepaintSelection(ToSelectionType(aRawSelectionType));
+}
+
+// Make shell be a document observer
+void PresShell::BeginObservingDocument() {
+ if (mDocument && !mIsDestroying) {
+ mIsObservingDocument = true;
+ if (mIsDocumentGone) {
+ NS_WARNING(
+ "Adding a presshell that was disconnected from the document "
+ "as a document observer? Sounds wrong...");
+ mIsDocumentGone = false;
+ }
+ }
+}
+
+// Make shell stop being a document observer
+void PresShell::EndObservingDocument() {
+ // XXXbz do we need to tell the frame constructor that the document
+ // is gone, perhaps? Except for printing it's NOT gone, sometimes.
+ mIsDocumentGone = true;
+ mIsObservingDocument = false;
+}
+
+#ifdef DEBUG_kipp
+char* nsPresShell_ReflowStackPointerTop;
+#endif
+
+void PresShell::InitPaintSuppressionTimer() {
+ // Default to PAINTLOCK_EVENT_DELAY if we can't get the pref value.
+ Document* doc = mDocument->GetDisplayDocument()
+ ? mDocument->GetDisplayDocument()
+ : mDocument.get();
+ const bool inProcess = !doc->GetBrowsingContext() ||
+ doc->GetBrowsingContext()->Top()->IsInProcess();
+ int32_t delay = inProcess
+ ? StaticPrefs::nglayout_initialpaint_delay()
+ : StaticPrefs::nglayout_initialpaint_delay_in_oopif();
+ mPaintSuppressionTimer->InitWithNamedFuncCallback(
+ [](nsITimer* aTimer, void* aPresShell) {
+ RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
+ self->UnsuppressPainting();
+ },
+ this, delay, nsITimer::TYPE_ONE_SHOT,
+ "PresShell::sPaintSuppressionCallback");
+}
+
+nsresult PresShell::Initialize() {
+ if (mIsDestroying) {
+ return NS_OK;
+ }
+
+ if (!mDocument) {
+ // Nothing to do
+ return NS_OK;
+ }
+
+ MOZ_LOG(gLog, LogLevel::Debug, ("PresShell::Initialize this=%p", this));
+
+ NS_ASSERTION(!mDidInitialize, "Why are we being called?");
+
+ RefPtr<PresShell> kungFuDeathGrip(this);
+
+ RecomputeFontSizeInflationEnabled();
+ MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
+
+ // Ensure the pres context doesn't think it has changed, since we haven't even
+ // started layout. This avoids spurious restyles / reflows afterwards.
+ //
+ // Note that this is very intentionally before setting mDidInitialize so it
+ // doesn't notify the document, or run media query change events.
+ mPresContext->FlushPendingMediaFeatureValuesChanged();
+ MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying);
+
+ mDidInitialize = true;
+
+#ifdef DEBUG
+ if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
+ if (mDocument) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ if (uri) {
+ printf("*** PresShell::Initialize (this=%p, url='%s')\n", (void*)this,
+ uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+#endif
+
+ // Get the root frame from the frame manager
+ // XXXbz it would be nice to move this somewhere else... like frame manager
+ // Init(), say. But we need to make sure our views are all set up by the
+ // time we do this!
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ NS_ASSERTION(!rootFrame, "How did that happen, exactly?");
+
+ if (!rootFrame) {
+ nsAutoScriptBlocker scriptBlocker;
+ rootFrame = mFrameConstructor->ConstructRootFrame();
+ mFrameConstructor->SetRootFrame(rootFrame);
+ }
+
+ NS_ENSURE_STATE(!mHaveShutDown);
+
+ if (!rootFrame) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (Element* root = mDocument->GetRootElement()) {
+ {
+ nsAutoCauseReflowNotifier reflowNotifier(this);
+ // Have the style sheet processor construct frame for the root
+ // content object down
+ mFrameConstructor->ContentInserted(
+ root, nsCSSFrameConstructor::InsertionKind::Sync);
+ }
+ // Something in mFrameConstructor->ContentInserted may have caused
+ // Destroy() to get called, bug 337586. Or, nsAutoCauseReflowNotifier
+ // (which sets up a script blocker) going out of scope may have killed us
+ // too
+ NS_ENSURE_STATE(!mHaveShutDown);
+ }
+
+ if (mDocument->HasAutoFocusCandidates()) {
+ mDocument->ScheduleFlushAutoFocusCandidates();
+ }
+
+ NS_ASSERTION(rootFrame, "How did that happen?");
+
+ // Note: when the frame was created above it had the NS_FRAME_IS_DIRTY bit
+ // set, but XBL processing could have caused a reflow which clears it.
+ if (MOZ_LIKELY(rootFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY))) {
+ // Unset the DIRTY bits so that FrameNeedsReflow() will work right.
+ rootFrame->RemoveStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN);
+ NS_ASSERTION(!mDirtyRoots.Contains(rootFrame),
+ "Why is the root in mDirtyRoots already?");
+ FrameNeedsReflow(rootFrame, IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
+ NS_ASSERTION(mDirtyRoots.Contains(rootFrame),
+ "Should be in mDirtyRoots now");
+ NS_ASSERTION(mObservingLayoutFlushes, "Why no reflow scheduled?");
+ }
+
+ // Restore our root scroll position now if we're getting here after EndLoad
+ // got called, since this is our one chance to do it. Note that we need not
+ // have reflowed for this to work; when the scrollframe is finally reflowed
+ // it'll pick up the position we store in it here.
+ if (!mDocumentLoading) {
+ RestoreRootScrollPosition();
+ }
+
+ // For printing, we just immediately unsuppress.
+ if (!mPresContext->IsPaginated()) {
+ // Kick off a one-shot timer based off our pref value. When this timer
+ // fires, if painting is still locked down, then we will go ahead and
+ // trigger a full invalidate and allow painting to proceed normally.
+ mPaintingSuppressed = true;
+ // Don't suppress painting if the document isn't loading.
+ Document::ReadyState readyState = mDocument->GetReadyStateEnum();
+ if (readyState != Document::READYSTATE_COMPLETE) {
+ mPaintSuppressionTimer = NS_NewTimer();
+ }
+ if (!mPaintSuppressionTimer) {
+ mPaintingSuppressed = false;
+ } else {
+ // Initialize the timer.
+ mPaintSuppressionTimer->SetTarget(GetMainThreadSerialEventTarget());
+ InitPaintSuppressionTimer();
+ if (mHasTriedFastUnsuppress) {
+ // Someone tried to unsuppress painting before Initialize was called so
+ // unsuppress painting rather soon.
+ mHasTriedFastUnsuppress = false;
+ TryUnsuppressPaintingSoon();
+ MOZ_ASSERT(mHasTriedFastUnsuppress);
+ }
+ }
+ }
+
+ // If we get here and painting is not suppressed, we still want to run the
+ // unsuppression logic, so set mShouldUnsuppressPainting to true.
+ if (!mPaintingSuppressed) {
+ mShouldUnsuppressPainting = true;
+ }
+
+ return NS_OK; // XXX this needs to be real. MMP
+}
+
+void PresShell::TryUnsuppressPaintingSoon() {
+ if (mHasTriedFastUnsuppress) {
+ return;
+ }
+ mHasTriedFastUnsuppress = true;
+
+ if (!mDidInitialize || !IsPaintingSuppressed() || !XRE_IsContentProcess()) {
+ return;
+ }
+
+ if (!mDocument->IsInitialDocument() &&
+ mDocument->DidHitCompleteSheetCache() &&
+ mPresContext->IsRootContentDocumentCrossProcess()) {
+ // Try to unsuppress faster on a top level page if it uses stylesheet
+ // cache, since that hints that many resources can be painted sooner than
+ // in a cold page load case.
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction("PresShell::TryUnsuppressPaintingSoon",
+ [self = RefPtr{this}]() -> void {
+ if (self->IsPaintingSuppressed()) {
+ PROFILER_MARKER_UNTYPED(
+ "Fast paint unsuppression", GRAPHICS);
+ self->UnsuppressPainting();
+ }
+ }),
+ EventQueuePriority::Control);
+ }
+}
+
+void PresShell::RefreshZoomConstraintsForScreenSizeChange() {
+ if (mZoomConstraintsClient) {
+ mZoomConstraintsClient->ScreenSizeChanged();
+ }
+}
+
+void PresShell::ForceResizeReflowWithCurrentDimensions() {
+ nscoord currentWidth = 0;
+ nscoord currentHeight = 0;
+ mViewManager->GetWindowDimensions(&currentWidth, &currentHeight);
+ ResizeReflow(currentWidth, currentHeight);
+}
+
+void PresShell::ResizeReflow(nscoord aWidth, nscoord aHeight,
+ ResizeReflowOptions aOptions) {
+ if (mZoomConstraintsClient) {
+ // If we have a ZoomConstraintsClient and the available screen area
+ // changed, then we might need to disable double-tap-to-zoom, so notify
+ // the ZCC to update itself.
+ mZoomConstraintsClient->ScreenSizeChanged();
+ }
+ if (UsesMobileViewportSizing()) {
+ // If we are using mobile viewport sizing, request a reflow from the MVM.
+ // It can recompute the final CSS viewport and trigger a call to
+ // ResizeReflowIgnoreOverride if it changed. We don't force adjusting
+ // of resolution, because that is only necessary when we are destroying
+ // the MVM.
+ MOZ_ASSERT(mMobileViewportManager);
+ mMobileViewportManager->RequestReflow(false);
+ return;
+ }
+ ResizeReflowIgnoreOverride(aWidth, aHeight, aOptions);
+}
+
+bool PresShell::SimpleResizeReflow(nscoord aWidth, nscoord aHeight) {
+ MOZ_ASSERT(aWidth != NS_UNCONSTRAINEDSIZE);
+ MOZ_ASSERT(aHeight != NS_UNCONSTRAINEDSIZE);
+ nsSize oldSize = mPresContext->GetVisibleArea().Size();
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ return false;
+ }
+ WritingMode wm = rootFrame->GetWritingMode();
+ bool isBSizeChanging =
+ wm.IsVertical() ? oldSize.width != aWidth : oldSize.height != aHeight;
+ if (isBSizeChanging) {
+ nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
+ }
+ FrameNeedsReflow(rootFrame, IntrinsicDirty::None,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ if (mMobileViewportManager) {
+ mMobileViewportManager->UpdateSizesBeforeReflow();
+ }
+ return true;
+}
+
+bool PresShell::CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent) {
+ if (XRE_IsParentProcess()) {
+ return true;
+ }
+
+ if (aGUIEvent->mFlags.mIsSynthesizedForTests &&
+ !StaticPrefs::dom_input_events_security_isUserInputHandlingDelayTest()) {
+ return true;
+ }
+
+ if (!aGUIEvent->IsUserAction()) {
+ return true;
+ }
+
+ if (nsPresContext* rootPresContext = mPresContext->GetRootPresContext()) {
+ return rootPresContext->UserInputEventsAllowed();
+ }
+
+ return true;
+}
+
+void PresShell::AddResizeEventFlushObserverIfNeeded() {
+ if (!mIsDestroying && !mResizeEventPending &&
+ MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
+ mResizeEventPending = true;
+ mPresContext->RefreshDriver()->AddResizeEventFlushObserver(this);
+ }
+}
+
+bool PresShell::ResizeReflowIgnoreOverride(nscoord aWidth, nscoord aHeight,
+ ResizeReflowOptions aOptions) {
+ MOZ_ASSERT(!mIsReflowing, "Shouldn't be in reflow here!");
+
+ // Historically we never fired resize events if there was no root frame by the
+ // time this function got called.
+ const bool initialized = mDidInitialize;
+ RefPtr<PresShell> kungFuDeathGrip(this);
+
+ auto postResizeEventIfNeeded = [this, initialized]() {
+ if (initialized) {
+ AddResizeEventFlushObserverIfNeeded();
+ }
+ };
+
+ if (!(aOptions & ResizeReflowOptions::BSizeLimit)) {
+ nsSize oldSize = mPresContext->GetVisibleArea().Size();
+ if (oldSize == nsSize(aWidth, aHeight)) {
+ return false;
+ }
+
+ bool changed = SimpleResizeReflow(aWidth, aHeight);
+ postResizeEventIfNeeded();
+ return changed;
+ }
+
+ // Make sure that style is flushed before setting the pres context
+ // VisibleArea.
+ //
+ // Otherwise we may end up with bogus viewport units resolved against the
+ // unconstrained bsize, or restyling the whole document resolving viewport
+ // units against targetWidth, which may end up doing wasteful work.
+ mDocument->FlushPendingNotifications(FlushType::Frames);
+
+ nsIFrame* rootFrame = GetRootFrame();
+ if (mIsDestroying || !rootFrame) {
+ // If we don't have a root frame yet, that means we haven't had our initial
+ // reflow... If that's the case, and aWidth or aHeight is unconstrained,
+ // ignore them altogether.
+ if (aHeight == NS_UNCONSTRAINEDSIZE || aWidth == NS_UNCONSTRAINEDSIZE) {
+ // We can't do the work needed for SizeToContent without a root
+ // frame, and we want to return before setting the visible area.
+ return false;
+ }
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+ // There isn't anything useful we can do if the initial reflow hasn't
+ // happened.
+ return true;
+ }
+
+ WritingMode wm = rootFrame->GetWritingMode();
+ MOZ_ASSERT((wm.IsVertical() ? aHeight : aWidth) != NS_UNCONSTRAINEDSIZE,
+ "unconstrained isize not allowed");
+
+ nscoord targetWidth = aWidth;
+ nscoord targetHeight = aHeight;
+ if (wm.IsVertical()) {
+ targetWidth = NS_UNCONSTRAINEDSIZE;
+ } else {
+ targetHeight = NS_UNCONSTRAINEDSIZE;
+ }
+
+ mPresContext->SetVisibleArea(nsRect(0, 0, targetWidth, targetHeight));
+ // XXX Do a full invalidate at the beginning so that invalidates along
+ // the way don't have region accumulation issues?
+
+ // For height:auto BSizes (i.e. layout-controlled), descendant
+ // intrinsic sizes can't depend on them. So the only other case is
+ // viewport-controlled BSizes which we handle here.
+ nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(rootFrame);
+
+ {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ WillDoReflow();
+
+ // Kick off a top-down reflow
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
+ nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
+
+ mDirtyRoots.Remove(rootFrame);
+ DoReflow(rootFrame, true, nullptr);
+
+ const bool reflowAgain =
+ wm.IsVertical() ? mPresContext->GetVisibleArea().width > aWidth
+ : mPresContext->GetVisibleArea().height > aHeight;
+
+ if (reflowAgain) {
+ mPresContext->SetVisibleArea(nsRect(0, 0, aWidth, aHeight));
+ DoReflow(rootFrame, true, nullptr);
+ }
+ }
+
+ // Now, we may have been destroyed by the destructor of
+ // `nsAutoCauseReflowNotifier`.
+
+ mPendingDidDoReflow = true;
+ DidDoReflow(true);
+
+ // the reflow above should've set our bsize if it was NS_UNCONSTRAINEDSIZE,
+ // and the isize shouldn't be NS_UNCONSTRAINEDSIZE anyway.
+ MOZ_DIAGNOSTIC_ASSERT(
+ mPresContext->GetVisibleArea().width != NS_UNCONSTRAINEDSIZE,
+ "width should not be NS_UNCONSTRAINEDSIZE after reflow");
+ MOZ_DIAGNOSTIC_ASSERT(
+ mPresContext->GetVisibleArea().height != NS_UNCONSTRAINEDSIZE,
+ "height should not be NS_UNCONSTRAINEDSIZE after reflow");
+
+ postResizeEventIfNeeded();
+ return true;
+}
+
+void PresShell::FireResizeEvent() {
+ if (mIsDocumentGone) {
+ return;
+ }
+
+ // If event handling is suppressed, repost the resize event to the refresh
+ // driver. The event is marked as delayed so that the refresh driver does not
+ // continue ticking.
+ if (mDocument->EventHandlingSuppressed()) {
+ if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
+ mDocument->SetHasDelayedRefreshEvent();
+ mPresContext->RefreshDriver()->AddResizeEventFlushObserver(
+ this, /* aDelayed = */ true);
+ }
+ return;
+ }
+
+ mResizeEventPending = false;
+ FireResizeEventSync();
+}
+
+void PresShell::FireResizeEventSync() {
+ if (mIsDocumentGone) {
+ return;
+ }
+
+ // Send resize event from here.
+ WidgetEvent event(true, mozilla::eResize);
+ nsEventStatus status = nsEventStatus_eIgnore;
+
+ if (RefPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ // MOZ_KnownLive due to bug 1506441
+ EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)),
+ mPresContext, &event, nullptr, &status);
+ }
+}
+
+static nsIContent* GetNativeAnonymousSubtreeRoot(nsIContent* aContent) {
+ if (!aContent) {
+ return nullptr;
+ }
+ return aContent->GetClosestNativeAnonymousSubtreeRoot();
+}
+
+void PresShell::NativeAnonymousContentRemoved(nsIContent* aAnonContent) {
+ MOZ_ASSERT(aAnonContent->IsRootOfNativeAnonymousSubtree());
+ mPresContext->EventStateManager()->NativeAnonymousContentRemoved(
+ aAnonContent);
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRemoved(this, aAnonContent);
+ }
+#endif
+ if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
+ aAnonContent->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ true);
+ }
+ if (nsIContent* root = GetNativeAnonymousSubtreeRoot(mCurrentEventContent)) {
+ if (aAnonContent == root) {
+ mCurrentEventContent = aAnonContent->GetFlattenedTreeParent();
+ mCurrentEventFrame = nullptr;
+ }
+ }
+
+ for (unsigned int i = 0; i < mCurrentEventContentStack.Length(); i++) {
+ nsIContent* anon =
+ GetNativeAnonymousSubtreeRoot(mCurrentEventContentStack.ElementAt(i));
+ if (aAnonContent == anon) {
+ mCurrentEventContentStack.ReplaceObjectAt(
+ aAnonContent->GetFlattenedTreeParent(), i);
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+ }
+}
+
+void PresShell::SetIgnoreFrameDestruction(bool aIgnore) {
+ if (mDocument) {
+ // We need to tell the ImageLoader to drop all its references to frames
+ // because they're about to go away and it won't get notifications of that.
+ mDocument->StyleImageLoader()->ClearFrames(mPresContext);
+ }
+ mIgnoreFrameDestruction = aIgnore;
+}
+
+void PresShell::NotifyDestroyingFrame(nsIFrame* aFrame) {
+ // We must remove these from FrameLayerBuilder::DisplayItemData::mFrameList
+ // here, otherwise the DisplayItemData destructor will use the destroyed frame
+ // when it tries to remove it from the (array) value of this property.
+ aFrame->RemoveDisplayItemDataForDeletion();
+
+ if (!mIgnoreFrameDestruction) {
+ if (aFrame->HasImageRequest()) {
+ mDocument->StyleImageLoader()->DropRequestsForFrame(aFrame);
+ }
+
+ mFrameConstructor->NotifyDestroyingFrame(aFrame);
+
+ mDirtyRoots.Remove(aFrame);
+
+ // Remove frame properties
+ aFrame->RemoveAllProperties();
+
+ if (aFrame == mCurrentEventFrame) {
+ mCurrentEventContent = aFrame->GetContent();
+ mCurrentEventFrame = nullptr;
+ }
+
+ for (unsigned int i = 0; i < mCurrentEventFrameStack.Length(); i++) {
+ if (aFrame == mCurrentEventFrameStack.ElementAt(i)) {
+ // One of our stack frames was deleted. Get its content so that when we
+ // pop it we can still get its new frame from its content
+ nsIContent* currentEventContent = aFrame->GetContent();
+ mCurrentEventContentStack.ReplaceObjectAt(currentEventContent, i);
+ mCurrentEventFrameStack[i] = nullptr;
+ }
+ }
+
+ mFramesToDirty.Remove(aFrame);
+
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(aFrame);
+ if (scrollableFrame) {
+ mPendingScrollAnchorSelection.Remove(scrollableFrame);
+ mPendingScrollAnchorAdjustment.Remove(scrollableFrame);
+ mPendingScrollResnap.Remove(scrollableFrame);
+ }
+ }
+}
+
+already_AddRefed<nsCaret> PresShell::GetCaret() const {
+ RefPtr<nsCaret> caret = mCaret;
+ return caret.forget();
+}
+
+already_AddRefed<AccessibleCaretEventHub>
+PresShell::GetAccessibleCaretEventHub() const {
+ RefPtr<AccessibleCaretEventHub> eventHub = mAccessibleCaretEventHub;
+ return eventHub.forget();
+}
+
+void PresShell::SetCaret(nsCaret* aNewCaret) { mCaret = aNewCaret; }
+
+void PresShell::RestoreCaret() { mCaret = mOriginalCaret; }
+
+NS_IMETHODIMP PresShell::SetCaretEnabled(bool aInEnable) {
+ bool oldEnabled = mCaretEnabled;
+
+ mCaretEnabled = aInEnable;
+
+ if (mCaretEnabled != oldEnabled) {
+ MOZ_ASSERT(mCaret);
+ if (mCaret) {
+ mCaret->SetVisible(mCaretEnabled);
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetCaretReadOnly(bool aReadOnly) {
+ if (mCaret) mCaret->SetCaretReadOnly(aReadOnly);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetCaretEnabled(bool* aOutEnabled) {
+ NS_ENSURE_ARG_POINTER(aOutEnabled);
+ *aOutEnabled = mCaretEnabled;
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetCaretVisibilityDuringSelection(bool aVisibility) {
+ if (mCaret) mCaret->SetVisibilityDuringSelection(aVisibility);
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetCaretVisible(bool* aOutIsVisible) {
+ *aOutIsVisible = false;
+ if (mCaret) {
+ *aOutIsVisible = mCaret->IsVisible();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::SetSelectionFlags(int16_t aFlags) {
+ mSelectionFlags = aFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP PresShell::GetSelectionFlags(int16_t* aFlags) {
+ if (!aFlags) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aFlags = mSelectionFlags;
+ return NS_OK;
+}
+
+// implementation of nsISelectionController
+
+NS_IMETHODIMP
+PresShell::PhysicalMove(int16_t aDirection, int16_t aAmount, bool aExtend) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->PhysicalMove(aDirection, aAmount, aExtend);
+}
+
+NS_IMETHODIMP
+PresShell::CharacterMove(bool aForward, bool aExtend) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->CharacterMove(aForward, aExtend);
+}
+
+NS_IMETHODIMP
+PresShell::WordMove(bool aForward, bool aExtend) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsresult result = frameSelection->WordMove(aForward, aExtend);
+ // if we can't go down/up any more we must then move caret completely to
+ // end/beginning respectively.
+ if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
+ return result;
+}
+
+NS_IMETHODIMP
+PresShell::LineMove(bool aForward, bool aExtend) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsresult result = frameSelection->LineMove(aForward, aExtend);
+ // if we can't go down/up any more we must then move caret completely to
+ // end/beginning respectively.
+ if (NS_FAILED(result)) result = CompleteMove(aForward, aExtend);
+ return result;
+}
+
+NS_IMETHODIMP
+PresShell::IntraLineMove(bool aForward, bool aExtend) {
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->IntraLineMove(aForward, aExtend);
+}
+
+NS_IMETHODIMP
+PresShell::PageMove(bool aForward, bool aExtend) {
+ nsIFrame* frame = nullptr;
+ if (!aExtend) {
+ frame = do_QueryFrame(GetScrollableFrameToScroll(VerticalScrollDirection));
+ // If there is no scrollable frame, get the frame to move caret instead.
+ }
+ if (!frame || frame->PresContext() != mPresContext) {
+ frame = mSelection->GetFrameToPageSelect();
+ if (!frame) {
+ return NS_OK;
+ }
+ }
+ // We may scroll parent scrollable element of current selection limiter.
+ // In such case, we don't want to scroll selection into view unless
+ // selection is changed.
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ return frameSelection->PageMove(
+ aForward, aExtend, frame, nsFrameSelection::SelectionIntoView::IfChanged);
+}
+
+NS_IMETHODIMP
+PresShell::ScrollPage(bool aForward) {
+ nsIScrollableFrame* scrollFrame =
+ GetScrollableFrameToScroll(VerticalScrollDirection);
+ ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Pages);
+ if (scrollFrame) {
+ scrollFrame->ScrollBy(nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::PAGES,
+ scrollMode, nullptr,
+ mozilla::ScrollOrigin::NotSpecified,
+ nsIScrollableFrame::NOT_MOMENTUM,
+ ScrollSnapFlags::IntendedDirection |
+ ScrollSnapFlags::IntendedEndPosition);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::ScrollLine(bool aForward) {
+ nsIScrollableFrame* scrollFrame =
+ GetScrollableFrameToScroll(VerticalScrollDirection);
+ ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
+ if (scrollFrame) {
+ nsRect scrollPort = scrollFrame->GetScrollPortRect();
+ nsSize lineSize = scrollFrame->GetLineScrollAmount();
+ int32_t lineCount = StaticPrefs::toolkit_scrollbox_verticalScrollDistance();
+ if (lineCount * lineSize.height > scrollPort.Height()) {
+ return ScrollPage(aForward);
+ }
+ scrollFrame->ScrollBy(
+ nsIntPoint(0, aForward ? lineCount : -lineCount), ScrollUnit::LINES,
+ scrollMode, nullptr, mozilla::ScrollOrigin::NotSpecified,
+ nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedDirection);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::ScrollCharacter(bool aRight) {
+ nsIScrollableFrame* scrollFrame =
+ GetScrollableFrameToScroll(HorizontalScrollDirection);
+ ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Lines);
+ if (scrollFrame) {
+ int32_t h = StaticPrefs::toolkit_scrollbox_horizontalScrollDistance();
+ scrollFrame->ScrollBy(
+ nsIntPoint(aRight ? h : -h, 0), ScrollUnit::LINES, scrollMode, nullptr,
+ mozilla::ScrollOrigin::NotSpecified, nsIScrollableFrame::NOT_MOMENTUM,
+ ScrollSnapFlags::IntendedDirection);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::CompleteScroll(bool aForward) {
+ nsIScrollableFrame* scrollFrame =
+ GetScrollableFrameToScroll(VerticalScrollDirection);
+ ScrollMode scrollMode = apz::GetScrollModeForOrigin(ScrollOrigin::Other);
+ if (scrollFrame) {
+ scrollFrame->ScrollBy(
+ nsIntPoint(0, aForward ? 1 : -1), ScrollUnit::WHOLE, scrollMode,
+ nullptr, mozilla::ScrollOrigin::NotSpecified,
+ nsIScrollableFrame::NOT_MOMENTUM, ScrollSnapFlags::IntendedEndPosition);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+PresShell::CompleteMove(bool aForward, bool aExtend) {
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ RefPtr<nsFrameSelection> frameSelection = mSelection;
+ nsIContent* limiter = frameSelection->GetAncestorLimiter();
+ nsIFrame* frame = limiter ? limiter->GetPrimaryFrame()
+ : FrameConstructor()->GetRootElementFrame();
+ if (!frame) return NS_ERROR_FAILURE;
+ nsIFrame::CaretPosition pos = frame->GetExtremeCaretPosition(!aForward);
+
+ const nsFrameSelection::FocusMode focusMode =
+ aExtend ? nsFrameSelection::FocusMode::kExtendSelection
+ : nsFrameSelection::FocusMode::kCollapseToNewPoint;
+ frameSelection->HandleClick(
+ MOZ_KnownLive(pos.mResultContent) /* bug 1636889 */, pos.mContentOffset,
+ pos.mContentOffset, focusMode,
+ aForward ? CaretAssociationHint::After : CaretAssociationHint::Before);
+ if (limiter) {
+ // HandleClick resets ancestorLimiter, so set it again.
+ frameSelection->SetAncestorLimiter(limiter);
+ }
+
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ return ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS |
+ nsISelectionController::SCROLL_FOR_CARET_MOVE);
+}
+
+// end implementations nsISelectionController
+
+nsIFrame* PresShell::GetRootScrollFrame() const {
+ if (!mFrameConstructor) {
+ return nullptr;
+ }
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ // Ensure root frame is a viewport frame
+ if (!rootFrame || !rootFrame->IsViewportFrame()) {
+ return nullptr;
+ }
+ nsIFrame* theFrame = rootFrame->PrincipalChildList().FirstChild();
+ if (!theFrame || !theFrame->IsScrollFrame()) {
+ return nullptr;
+ }
+ return theFrame;
+}
+
+nsIScrollableFrame* PresShell::GetRootScrollFrameAsScrollable() const {
+ nsIFrame* frame = GetRootScrollFrame();
+ if (!frame) {
+ return nullptr;
+ }
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(frame);
+ NS_ASSERTION(scrollableFrame,
+ "All scroll frames must implement nsIScrollableFrame");
+ return scrollableFrame;
+}
+
+nsPageSequenceFrame* PresShell::GetPageSequenceFrame() const {
+ return mFrameConstructor->GetPageSequenceFrame();
+}
+
+nsCanvasFrame* PresShell::GetCanvasFrame() const {
+ return mFrameConstructor->GetCanvasFrame();
+}
+
+void PresShell::RestoreRootScrollPosition() {
+ nsIScrollableFrame* scrollableFrame = GetRootScrollFrameAsScrollable();
+ if (scrollableFrame) {
+ scrollableFrame->ScrollToRestoredPosition();
+ }
+}
+
+void PresShell::MaybeReleaseCapturingContent() {
+ RefPtr<nsFrameSelection> frameSelection = FrameSelection();
+ if (frameSelection) {
+ frameSelection->SetDragState(false);
+ }
+ if (sCapturingContentInfo.mContent &&
+ sCapturingContentInfo.mContent->OwnerDoc() == mDocument) {
+ PresShell::ReleaseCapturingContent();
+ }
+}
+
+void PresShell::BeginLoad(Document* aDocument) {
+ mDocumentLoading = true;
+
+ gfxTextPerfMetrics* tp = nullptr;
+ if (mPresContext) {
+ tp = mPresContext->GetTextPerfMetrics();
+ }
+
+ bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
+ if (shouldLog || tp) {
+ mLoadBegin = TimeStamp::Now();
+ }
+
+ if (shouldLog) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("(presshell) %p load begin [%s]\n", this,
+ uri ? uri->GetSpecOrDefault().get() : ""));
+ }
+}
+
+void PresShell::EndLoad(Document* aDocument) {
+ MOZ_ASSERT(aDocument == mDocument, "Wrong document");
+
+ RestoreRootScrollPosition();
+
+ mDocumentLoading = false;
+}
+
+bool PresShell::IsLayoutFlushObserver() {
+ return GetPresContext()->RefreshDriver()->IsLayoutFlushObserver(this);
+}
+
+void PresShell::LoadComplete() {
+ gfxTextPerfMetrics* tp = nullptr;
+ if (mPresContext) {
+ tp = mPresContext->GetTextPerfMetrics();
+ }
+
+ // log load
+ bool shouldLog = MOZ_LOG_TEST(gLog, LogLevel::Debug);
+ if (shouldLog || tp) {
+ TimeDuration loadTime = TimeStamp::Now() - mLoadBegin;
+ nsIURI* uri = mDocument->GetDocumentURI();
+ nsAutoCString spec;
+ if (uri) {
+ spec = uri->GetSpecOrDefault();
+ }
+ if (shouldLog) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("(presshell) %p load done time-ms: %9.2f [%s]\n", this,
+ loadTime.ToMilliseconds(), spec.get()));
+ }
+ if (tp) {
+ tp->Accumulate();
+ if (tp->cumulative.numChars > 0) {
+ LogTextPerfStats(tp, this, tp->cumulative, loadTime.ToMilliseconds(),
+ eLog_loaddone, spec.get());
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+void PresShell::VerifyHasDirtyRootAncestor(nsIFrame* aFrame) {
+ // XXXbz due to bug 372769, can't actually assert anything here...
+ // XXX Since bug 372769 is now fixed, the assertion is being enabled in bug
+ // 1758104.
+# if 0
+ // XXXbz shouldn't need this part; remove it once FrameNeedsReflow
+ // handles the root frame correctly.
+ if (!aFrame->GetParent()) {
+ return;
+ }
+
+ // Make sure that there is a reflow root ancestor of |aFrame| that's
+ // in mDirtyRoots already.
+ while (aFrame && aFrame->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN)) {
+ if ((aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
+ NS_FRAME_DYNAMIC_REFLOW_ROOT) ||
+ !aFrame->GetParent()) &&
+ mDirtyRoots.Contains(aFrame)) {
+ return;
+ }
+
+ aFrame = aFrame->GetParent();
+ }
+
+ MOZ_ASSERT_UNREACHABLE(
+ "Frame has dirty bits set but isn't scheduled to be "
+ "reflowed?");
+# endif
+}
+#endif
+
+void PresShell::PostPendingScrollAnchorSelection(
+ mozilla::layout::ScrollAnchorContainer* aContainer) {
+ mPendingScrollAnchorSelection.Insert(aContainer->ScrollableFrame());
+}
+
+void PresShell::FlushPendingScrollAnchorSelections() {
+ for (nsIScrollableFrame* scroll : mPendingScrollAnchorSelection) {
+ scroll->Anchor()->SelectAnchor();
+ }
+ mPendingScrollAnchorSelection.Clear();
+}
+
+void PresShell::PostPendingScrollAnchorAdjustment(
+ ScrollAnchorContainer* aContainer) {
+ mPendingScrollAnchorAdjustment.Insert(aContainer->ScrollableFrame());
+}
+
+void PresShell::FlushPendingScrollAnchorAdjustments() {
+ for (nsIScrollableFrame* scroll : mPendingScrollAnchorAdjustment) {
+ scroll->Anchor()->ApplyAdjustments();
+ }
+ mPendingScrollAnchorAdjustment.Clear();
+}
+
+void PresShell::PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame) {
+ mPendingScrollResnap.Insert(aScrollableFrame);
+}
+
+void PresShell::FlushPendingScrollResnap() {
+ for (nsIScrollableFrame* scrollableFrame : mPendingScrollResnap) {
+ scrollableFrame->TryResnap();
+ }
+ mPendingScrollResnap.Clear();
+}
+
+void PresShell::FrameNeedsReflow(nsIFrame* aFrame,
+ IntrinsicDirty aIntrinsicDirty,
+ nsFrameState aBitToAdd,
+ ReflowRootHandling aRootHandling) {
+ MOZ_ASSERT(aBitToAdd == NS_FRAME_IS_DIRTY ||
+ aBitToAdd == NS_FRAME_HAS_DIRTY_CHILDREN || !aBitToAdd,
+ "Unexpected bits being added");
+
+ // FIXME bug 478135
+ NS_ASSERTION(
+ aIntrinsicDirty != IntrinsicDirty::FrameAncestorsAndDescendants ||
+ aBitToAdd != NS_FRAME_HAS_DIRTY_CHILDREN,
+ "bits don't correspond to style change reason");
+
+ // FIXME bug 457400
+ NS_ASSERTION(!mIsReflowing, "can't mark frame dirty during reflow");
+
+ // If we've not yet done the initial reflow, then don't bother
+ // enqueuing a reflow command yet.
+ if (!mDidInitialize) return;
+
+ // If we're already destroying, don't bother with this either.
+ if (mIsDestroying) return;
+
+#ifdef DEBUG
+ // printf("gShellCounter: %d\n", gShellCounter++);
+ if (mInVerifyReflow) return;
+
+ if (VerifyReflowFlags::NoisyCommands & gVerifyReflowFlags) {
+ printf("\nPresShell@%p: frame %p needs reflow\n", (void*)this,
+ (void*)aFrame);
+ if (VerifyReflowFlags::ReallyNoisyCommands & gVerifyReflowFlags) {
+ printf("Current content model:\n");
+ Element* rootElement = mDocument->GetRootElement();
+ if (rootElement) {
+ rootElement->List(stdout, 0);
+ }
+ }
+ }
+#endif
+
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aFrame);
+
+ do {
+ nsIFrame* subtreeRoot = subtrees.PopLastElement();
+
+ // Grab |wasDirty| now so we can go ahead and update the bits on
+ // subtreeRoot.
+ bool wasDirty = subtreeRoot->IsSubtreeDirty();
+ subtreeRoot->AddStateBits(aBitToAdd);
+
+ // Determine whether we need to keep looking for the next ancestor
+ // reflow root if subtreeRoot itself is a reflow root.
+ bool targetNeedsReflowFromParent;
+ switch (aRootHandling) {
+ case ReflowRootHandling::PositionOrSizeChange:
+ targetNeedsReflowFromParent = true;
+ break;
+ case ReflowRootHandling::NoPositionOrSizeChange:
+ targetNeedsReflowFromParent = false;
+ break;
+ case ReflowRootHandling::InferFromBitToAdd:
+ targetNeedsReflowFromParent = (aBitToAdd == NS_FRAME_IS_DIRTY);
+ break;
+ }
+
+ auto FrameIsReflowRoot = [](const nsIFrame* aFrame) {
+ return aFrame->HasAnyStateBits(NS_FRAME_REFLOW_ROOT |
+ NS_FRAME_DYNAMIC_REFLOW_ROOT);
+ };
+
+ auto CanStopClearingAncestorIntrinsics = [&](const nsIFrame* aFrame) {
+ return FrameIsReflowRoot(aFrame) && aFrame != subtreeRoot;
+ };
+
+ auto IsReflowBoundary = [&](const nsIFrame* aFrame) {
+ return FrameIsReflowRoot(aFrame) &&
+ (aFrame != subtreeRoot || !targetNeedsReflowFromParent);
+ };
+
+ // Mark the intrinsic widths as dirty on the frame, all of its ancestors,
+ // and all of its descendants, if needed:
+
+ if (aIntrinsicDirty != IntrinsicDirty::None) {
+ // Mark argument and all ancestors dirty. (Unless we hit a reflow root
+ // that should contain the reflow.
+ for (nsIFrame* a = subtreeRoot;
+ a && !CanStopClearingAncestorIntrinsics(a); a = a->GetParent()) {
+ a->MarkIntrinsicISizesDirty();
+ if (a->IsAbsolutelyPositioned()) {
+ // If we get here, 'a' is abspos, so its subtree's intrinsic sizing
+ // has no effect on its ancestors' intrinsic sizing. So, don't loop
+ // upwards any further.
+ break;
+ }
+ }
+ }
+
+ const bool frameAncestorAndDescendantISizesDirty =
+ (aIntrinsicDirty == IntrinsicDirty::FrameAncestorsAndDescendants);
+ const bool dirty = (aBitToAdd == NS_FRAME_IS_DIRTY);
+ if (frameAncestorAndDescendantISizesDirty || dirty) {
+ // Mark all descendants dirty (using an nsTArray stack rather than
+ // recursion).
+ // Note that ReflowInput::InitResizeFlags has some similar
+ // code; see comments there for how and why it differs.
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(subtreeRoot);
+
+ do {
+ nsIFrame* f = stack.PopLastElement();
+
+ if (frameAncestorAndDescendantISizesDirty && f->IsPlaceholderFrame()) {
+ // Call `GetOutOfFlowFrame` directly because we can get here from
+ // frame destruction and the placeholder might be already torn down.
+ if (nsIFrame* oof =
+ static_cast<nsPlaceholderFrame*>(f)->GetOutOfFlowFrame()) {
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+ }
+
+ for (const auto& childList : f->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ if (frameAncestorAndDescendantISizesDirty) {
+ kid->MarkIntrinsicISizesDirty();
+ }
+ if (dirty) {
+ kid->AddStateBits(NS_FRAME_IS_DIRTY);
+ }
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+ }
+
+ // Skip setting dirty bits up the tree if we weren't given a bit to add.
+ if (!aBitToAdd) {
+ continue;
+ }
+
+ // Set NS_FRAME_HAS_DIRTY_CHILDREN bits (via nsIFrame::ChildIsDirty)
+ // up the tree until we reach either a frame that's already dirty or
+ // a reflow root.
+ nsIFrame* f = subtreeRoot;
+ for (;;) {
+ if (IsReflowBoundary(f) || !f->GetParent()) {
+ // we've hit a reflow root or the root frame
+ if (!wasDirty) {
+ mDirtyRoots.Add(f);
+ SetNeedLayoutFlush();
+ }
+#ifdef DEBUG
+ else {
+ VerifyHasDirtyRootAncestor(f);
+ }
+#endif
+
+ break;
+ }
+
+ nsIFrame* child = f;
+ f = f->GetParent();
+ wasDirty = f->IsSubtreeDirty();
+ f->ChildIsDirty(child);
+ NS_ASSERTION(f->HasAnyStateBits(NS_FRAME_HAS_DIRTY_CHILDREN),
+ "ChildIsDirty didn't do its job");
+ if (wasDirty) {
+ // This frame was already marked dirty.
+#ifdef DEBUG
+ VerifyHasDirtyRootAncestor(f);
+#endif
+ break;
+ }
+ }
+ } while (subtrees.Length() != 0);
+
+ MaybeScheduleReflow();
+}
+
+void PresShell::FrameNeedsToContinueReflow(nsIFrame* aFrame) {
+ NS_ASSERTION(mIsReflowing, "Must be in reflow when marking path dirty.");
+ MOZ_ASSERT(mCurrentReflowRoot, "Must have a current reflow root here");
+ NS_ASSERTION(
+ aFrame == mCurrentReflowRoot ||
+ nsLayoutUtils::IsProperAncestorFrame(mCurrentReflowRoot, aFrame),
+ "Frame passed in is not the descendant of mCurrentReflowRoot");
+ NS_ASSERTION(aFrame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
+ "Frame passed in not in reflow?");
+
+ mFramesToDirty.Insert(aFrame);
+}
+
+already_AddRefed<nsIContent> PresShell::GetContentForScrolling() const {
+ if (nsCOMPtr<nsIContent> focused = GetFocusedContentInOurWindow()) {
+ return focused.forget();
+ }
+ return GetSelectedContentForScrolling();
+}
+
+already_AddRefed<nsIContent> PresShell::GetSelectedContentForScrolling() const {
+ nsCOMPtr<nsIContent> selectedContent;
+ if (mSelection) {
+ Selection* domSelection = mSelection->GetSelection(SelectionType::eNormal);
+ if (domSelection) {
+ selectedContent =
+ nsIContent::FromNodeOrNull(domSelection->GetFocusNode());
+ }
+ }
+ return selectedContent.forget();
+}
+
+nsIScrollableFrame* PresShell::GetScrollableFrameToScrollForContent(
+ nsIContent* aContent, ScrollDirections aDirections) {
+ nsIScrollableFrame* scrollFrame = nullptr;
+ if (aContent) {
+ nsIFrame* startFrame = aContent->GetPrimaryFrame();
+ if (startFrame) {
+ scrollFrame = startFrame->GetScrollTargetFrame();
+ if (scrollFrame) {
+ startFrame = scrollFrame->GetScrolledFrame();
+ }
+ scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
+ startFrame, aDirections);
+ }
+ }
+ if (!scrollFrame) {
+ scrollFrame = GetRootScrollFrameAsScrollable();
+ if (!scrollFrame || !scrollFrame->GetScrolledFrame()) {
+ return nullptr;
+ }
+ scrollFrame = nsLayoutUtils::GetNearestScrollableFrameForDirection(
+ scrollFrame->GetScrolledFrame(), aDirections);
+ }
+ return scrollFrame;
+}
+
+nsIScrollableFrame* PresShell::GetScrollableFrameToScroll(
+ ScrollDirections aDirections) {
+ nsCOMPtr<nsIContent> content = GetContentForScrolling();
+ return GetScrollableFrameToScrollForContent(content.get(), aDirections);
+}
+
+void PresShell::CancelAllPendingReflows() {
+ mDirtyRoots.Clear();
+
+ if (mObservingLayoutFlushes) {
+ GetPresContext()->RefreshDriver()->RemoveLayoutFlushObserver(this);
+ mObservingLayoutFlushes = false;
+ }
+
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+static bool DestroyFramesAndStyleDataFor(
+ Element* aElement, nsPresContext& aPresContext,
+ RestyleManager::IncludeRoot aIncludeRoot) {
+ bool didReconstruct =
+ aPresContext.FrameConstructor()->DestroyFramesFor(aElement);
+ RestyleManager::ClearServoDataFromSubtree(aElement, aIncludeRoot);
+ return didReconstruct;
+}
+
+void PresShell::SlotAssignmentWillChange(Element& aElement,
+ HTMLSlotElement* aOldSlot,
+ HTMLSlotElement* aNewSlot) {
+ MOZ_ASSERT(aOldSlot != aNewSlot);
+
+ if (MOZ_UNLIKELY(!mDidInitialize)) {
+ return;
+ }
+
+ // If the old slot is about to become empty and show fallback, let layout know
+ // that it needs to do work.
+ if (aOldSlot && aOldSlot->AssignedNodes().Length() == 1 &&
+ aOldSlot->HasChildren()) {
+ DestroyFramesForAndRestyle(aOldSlot);
+ }
+
+ // Ensure the new element starts off clean.
+ DestroyFramesAndStyleDataFor(&aElement, *mPresContext,
+ RestyleManager::IncludeRoot::Yes);
+
+ if (aNewSlot) {
+ // If the new slot will stop showing fallback content, we need to reframe it
+ // altogether.
+ if (aNewSlot->AssignedNodes().IsEmpty() && aNewSlot->HasChildren()) {
+ DestroyFramesForAndRestyle(aNewSlot);
+ // Otherwise we just care about the element, but we need to ensure that
+ // something takes care of traversing to the relevant slot, if needed.
+ } else if (aNewSlot->HasServoData() &&
+ !Servo_Element_IsDisplayNone(aNewSlot)) {
+ // Set the reframe bits...
+ aNewSlot->NoteDescendantsNeedFramesForServo();
+ aElement.SetFlags(NODE_NEEDS_FRAME);
+ // Now the style dirty bits. Note that we can't just do
+ // aElement.NoteDirtyForServo(), because the new slot is not setup yet.
+ aNewSlot->SetHasDirtyDescendantsForServo();
+ aNewSlot->NoteDirtySubtreeForServo();
+ }
+ }
+}
+
+#ifdef DEBUG
+static void AssertNoFramesOrStyleDataInDescendants(Element& aElement) {
+ for (nsINode* node : ShadowIncludingTreeIterator(aElement)) {
+ nsIContent* c = nsIContent::FromNode(node);
+ if (c == &aElement) {
+ continue;
+ }
+ // FIXME(emilio): The <area> check is needed because of bug 135040.
+ MOZ_ASSERT(!c->GetPrimaryFrame() || c->IsHTMLElement(nsGkAtoms::area));
+ MOZ_ASSERT(!c->IsElement() || !c->AsElement()->HasServoData());
+ }
+}
+#endif
+
+void PresShell::DestroyFramesForAndRestyle(Element* aElement) {
+#ifdef DEBUG
+ auto postCondition = MakeScopeExit([&]() {
+ MOZ_ASSERT(!aElement->GetPrimaryFrame());
+ AssertNoFramesOrStyleDataInDescendants(*aElement);
+ });
+#endif
+
+ MOZ_ASSERT(aElement);
+ if (!aElement->HasServoData()) {
+ // Nothing to do here, the element already is out of the flat tree or is not
+ // styled.
+ return;
+ }
+
+ // Mark ourselves as not safe to flush while we're doing frame destruction.
+ nsAutoScriptBlocker scriptBlocker;
+ ++mChangeNestCount;
+
+ const bool didReconstruct = FrameConstructor()->DestroyFramesFor(aElement);
+ // Clear the style data from all the flattened tree descendants, but _not_
+ // from us, since otherwise we wouldn't see the reframe.
+ RestyleManager::ClearServoDataFromSubtree(aElement,
+ RestyleManager::IncludeRoot::No);
+ auto changeHint =
+ didReconstruct ? nsChangeHint(0) : nsChangeHint_ReconstructFrame;
+ mPresContext->RestyleManager()->PostRestyleEvent(
+ aElement, RestyleHint::RestyleSubtree(), changeHint);
+
+ --mChangeNestCount;
+}
+
+void PresShell::ShadowRootWillBeAttached(Element& aElement) {
+#ifdef DEBUG
+ auto postCondition = MakeScopeExit(
+ [&]() { AssertNoFramesOrStyleDataInDescendants(aElement); });
+#endif
+
+ if (!aElement.HasServoData()) {
+ // Nothing to do here, the element already is out of the flat tree or is not
+ // styled.
+ return;
+ }
+
+ if (!aElement.HasChildren()) {
+ // The element has no children, just avoid the work.
+ return;
+ }
+
+ // Mark ourselves as not safe to flush while we're doing frame destruction.
+ nsAutoScriptBlocker scriptBlocker;
+ ++mChangeNestCount;
+
+ // NOTE(emilio): We use FlattenedChildIterator intentionally here (rather than
+ // StyleChildrenIterator), since we don't want to remove ::before / ::after
+ // content.
+ FlattenedChildIterator iter(&aElement);
+ nsCSSFrameConstructor* fc = FrameConstructor();
+ for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
+ fc->DestroyFramesFor(c);
+ if (c->IsElement()) {
+ RestyleManager::ClearServoDataFromSubtree(c->AsElement());
+ }
+ }
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ScheduleAccessibilitySubtreeUpdate(this, &aElement);
+ }
+#endif
+
+ --mChangeNestCount;
+}
+
+void PresShell::PostRecreateFramesFor(Element* aElement) {
+ if (MOZ_UNLIKELY(!mDidInitialize)) {
+ // Nothing to do here. In fact, if we proceed and aElement is the root, we
+ // will crash.
+ return;
+ }
+
+ mPresContext->RestyleManager()->PostRestyleEvent(
+ aElement, RestyleHint{0}, nsChangeHint_ReconstructFrame);
+}
+
+void PresShell::RestyleForAnimation(Element* aElement, RestyleHint aHint) {
+ // Now that we no longer have separate non-animation and animation
+ // restyles, this method having a distinct identity is less important,
+ // but it still seems useful to offer as a "more public" API and as a
+ // checkpoint for these restyles to go through.
+ mPresContext->RestyleManager()->PostRestyleEvent(aElement, aHint,
+ nsChangeHint(0));
+}
+
+void PresShell::SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer) {
+ mForwardingContainer = aContainer;
+}
+
+void PresShell::ClearFrameRefs(nsIFrame* aFrame) {
+ mPresContext->EventStateManager()->ClearFrameRefs(aFrame);
+
+ AutoWeakFrame* weakFrame = mAutoWeakFrames;
+ while (weakFrame) {
+ AutoWeakFrame* prev = weakFrame->GetPreviousWeakFrame();
+ if (weakFrame->GetFrame() == aFrame) {
+ // This removes weakFrame from mAutoWeakFrames.
+ weakFrame->Clear(this);
+ }
+ weakFrame = prev;
+ }
+
+ AutoTArray<WeakFrame*, 4> toRemove;
+ for (WeakFrame* weakFrame : mWeakFrames) {
+ if (weakFrame->GetFrame() == aFrame) {
+ toRemove.AppendElement(weakFrame);
+ }
+ }
+ for (WeakFrame* weakFrame : toRemove) {
+ weakFrame->Clear(this);
+ }
+}
+
+UniquePtr<gfxContext> PresShell::CreateReferenceRenderingContext() {
+ if (mPresContext->IsScreen()) {
+ return gfxContext::CreateOrNull(
+ gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget().get());
+ }
+
+ // We assume the devCtx has positive width and height for this call.
+ // However, width and height, may be outside of the reasonable range
+ // so rc may still be null.
+ nsDeviceContext* devCtx = mPresContext->DeviceContext();
+ return devCtx->CreateReferenceRenderingContext();
+}
+
+// https://html.spec.whatwg.org/#scroll-to-the-fragment-identifier
+nsresult PresShell::GoToAnchor(const nsAString& aAnchorName, bool aScroll,
+ ScrollFlags aAdditionalScrollFlags) {
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const Element* root = mDocument->GetRootElement();
+ if (root && root->IsSVGElement(nsGkAtoms::svg)) {
+ // We need to execute this even if there is an empty anchor name
+ // so that any existing SVG fragment identifier effect is removed
+ if (SVGFragmentIdentifier::ProcessFragmentIdentifier(mDocument,
+ aAnchorName)) {
+ return NS_OK;
+ }
+ }
+
+ // Hold a reference to the ESM in case event dispatch tears us down.
+ RefPtr<EventStateManager> esm = mPresContext->EventStateManager();
+
+ // 1. If there is no indicated part of the document, set the Document's target
+ // element to null.
+ //
+ // FIXME(emilio): Per spec empty fragment string should take the same
+ // code-path as "top"!
+ if (aAnchorName.IsEmpty()) {
+ NS_ASSERTION(!aScroll, "can't scroll to empty anchor name");
+ esm->SetContentState(nullptr, ElementState::URLTARGET);
+ return NS_OK;
+ }
+
+ // 2. If the indicated part of the document is the top of the document,
+ // then:
+ // (handled below when `target` is null, and anchor is `top`)
+
+ // 3.1. Let target be element that is the indicated part of the document.
+ //
+ // https://html.spec.whatwg.org/#target-element
+ // https://html.spec.whatwg.org/#find-a-potential-indicated-element
+ RefPtr<Element> target =
+ nsContentUtils::GetTargetElement(mDocument, aAnchorName);
+
+ // 1. If there is no indicated part of the document, set the Document's
+ // target element to null.
+ // 2.1. Set the Document's target element to null.
+ // 3.2. Set the Document's target element to target.
+ esm->SetContentState(target, ElementState::URLTARGET);
+
+ // TODO: Spec probably needs a section to account for this.
+ if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
+ if (rootScroll->DidHistoryRestore()) {
+ // Scroll position restored from history trumps scrolling to anchor.
+ aScroll = false;
+ rootScroll->ClearDidHistoryRestore();
+ }
+ }
+
+ if (target) {
+ if (aScroll) {
+ // 3.3. TODO: Run the ancestor details revealing algorithm on target.
+ // 3.4. Scroll target into view, with behavior set to "auto", block set to
+ // "start", and inline set to "nearest".
+ // FIXME(emilio): Not all callers pass ScrollSmoothAuto (but we use auto
+ // smooth scroll for `top` regardless below, so maybe they should!).
+ ScrollingInteractionContext scrollToAnchorContext(true);
+ MOZ_TRY(ScrollContentIntoView(
+ target, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
+ ScrollAxis(),
+ ScrollFlags::AnchorScrollFlags | aAdditionalScrollFlags));
+
+ if (nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable()) {
+ mLastAnchorScrolledTo = target;
+ mLastAnchorScrollPositionY = rootScroll->GetScrollPosition().y;
+ }
+ }
+
+ {
+ // 3.6. Move the sequential focus navigation starting point to target.
+ //
+ // Move the caret to the anchor. That way tabbing will start from the new
+ // location.
+ //
+ // TODO(emilio): Do we want to do this even if aScroll is false?
+ //
+ // NOTE: Intentionally out of order for now with the focus steps, see
+ // https://github.com/whatwg/html/issues/7759
+ RefPtr<nsRange> jumpToRange = nsRange::Create(mDocument);
+ nsCOMPtr<nsIContent> nodeToSelect = target.get();
+ while (nodeToSelect->GetFirstChild()) {
+ nodeToSelect = nodeToSelect->GetFirstChild();
+ }
+ jumpToRange->SelectNodeContents(*nodeToSelect, IgnoreErrors());
+ if (RefPtr sel = mSelection->GetSelection(SelectionType::eNormal)) {
+ sel->RemoveAllRanges(IgnoreErrors());
+ sel->AddRangeAndSelectFramesAndNotifyListeners(*jumpToRange,
+ IgnoreErrors());
+ if (!StaticPrefs::layout_selectanchor()) {
+ // Use a caret (collapsed selection) at the start of the anchor.
+ sel->CollapseToStart(IgnoreErrors());
+ }
+ }
+ }
+
+ // 3.5. Run the focusing steps for target, with the Document's viewport as
+ // the fallback target.
+ //
+ // Note that ScrollContentIntoView flushes, so we don't need to do that
+ // again here. We also don't need to scroll again either.
+ //
+ // We intentionally focus the target only when aScroll is true, we need to
+ // sort out if the spec needs to differentiate these cases. When aScroll is
+ // false we still clear the focus unconditionally, that's legacy behavior,
+ // maybe we shouldn't do it.
+ //
+ // TODO(emilio): Do we really want to clear the focus even if aScroll is
+ // false?
+ const bool shouldFocusTarget = [&] {
+ if (!aScroll) {
+ return false;
+ }
+ nsIFrame* targetFrame = target->GetPrimaryFrame();
+ return targetFrame && targetFrame->IsFocusable();
+ }();
+
+ if (shouldFocusTarget) {
+ FocusOptions options;
+ options.mPreventScroll = true;
+ target->Focus(options, CallerType::NonSystem, IgnoreErrors());
+ } else if (RefPtr<nsIFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
+ // Now focus the document itself if focus is on an element within it.
+ nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+ fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+ if (SameCOMIdentity(win, focusedWindow)) {
+ fm->ClearFocus(focusedWindow);
+ }
+ }
+ }
+
+ // If the target is an animation element, activate the animation
+ if (auto* animationElement = SVGAnimationElement::FromNode(target.get())) {
+ animationElement->ActivateByHyperlink();
+ }
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfAnchorJumpTo(target);
+ }
+#endif
+ } else if (nsContentUtils::EqualsIgnoreASCIICase(aAnchorName, u"top"_ns)) {
+ // 2.2. Scroll to the beginning of the document for the Document.
+ nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable();
+ // Check |aScroll| after setting |rv| so we set |rv| to the same
+ // thing whether or not |aScroll| is true.
+ if (aScroll && sf) {
+ ScrollMode scrollMode =
+ sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant;
+ // Scroll to the top of the page
+ sf->ScrollTo(nsPoint(0, 0), scrollMode);
+ }
+ } else {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult PresShell::ScrollToAnchor() {
+ nsCOMPtr<nsIContent> lastAnchor = std::move(mLastAnchorScrolledTo);
+ if (!lastAnchor) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+ nsIScrollableFrame* rootScroll = GetRootScrollFrameAsScrollable();
+ if (!rootScroll ||
+ mLastAnchorScrollPositionY != rootScroll->GetScrollPosition().y) {
+ return NS_OK;
+ }
+ return ScrollContentIntoView(
+ lastAnchor, ScrollAxis(WhereToScroll::Start, WhenToScroll::Always),
+ ScrollAxis(), ScrollFlags::AnchorScrollFlags);
+}
+
+/*
+ * Helper (per-continuation) for ScrollContentIntoView.
+ *
+ * @param aContainerFrame [in] the frame which aRect is relative to
+ * @param aFrame [in] Frame whose bounds should be unioned
+ * @param aUseWholeLineHeightForInlines [in] if true, then for inline frames
+ * we should include the top of the line in the added rectangle
+ * @param aRect [inout] rect into which its bounds should be unioned
+ * @param aHaveRect [inout] whether aRect contains data yet
+ * @param aPrevBlock [inout] the block aLines is a line iterator for
+ * @param aLines [inout] the line iterator we're using
+ * @param aCurLine [inout] the line to start looking from in this iterator
+ */
+static void AccumulateFrameBounds(nsIFrame* aContainerFrame, nsIFrame* aFrame,
+ bool aUseWholeLineHeightForInlines,
+ nsRect& aRect, bool& aHaveRect,
+ nsIFrame*& aPrevBlock,
+ nsILineIterator*& aLines, int32_t& aCurLine) {
+ nsIFrame* frame = aFrame;
+ nsRect frameBounds = nsRect(nsPoint(0, 0), aFrame->GetSize());
+
+ // If this is an inline frame and either the bounds height is 0 (quirks
+ // layout model) or aUseWholeLineHeightForInlines is set, we need to
+ // change the top of the bounds to include the whole line.
+ if (frameBounds.height == 0 || aUseWholeLineHeightForInlines) {
+ nsIFrame* prevFrame = aFrame;
+ nsIFrame* f = aFrame;
+
+ while (f && f->IsLineParticipant() && !f->IsTransformed() &&
+ !f->IsAbsPosContainingBlock()) {
+ prevFrame = f;
+ f = prevFrame->GetParent();
+ }
+
+ if (f != aFrame && f && f->IsBlockFrame()) {
+ // find the line containing aFrame and increase the top of |offset|.
+ if (f != aPrevBlock) {
+ aLines = f->GetLineIterator();
+ aPrevBlock = f;
+ aCurLine = 0;
+ }
+ if (aLines) {
+ int32_t index = aLines->FindLineContaining(prevFrame, aCurLine);
+ if (index >= 0) {
+ auto line = aLines->GetLine(index).unwrap();
+ frameBounds += frame->GetOffsetTo(f);
+ frame = f;
+ if (line.mLineBounds.y < frameBounds.y) {
+ frameBounds.height = frameBounds.YMost() - line.mLineBounds.y;
+ frameBounds.y = line.mLineBounds.y;
+ }
+ }
+ }
+ }
+ }
+
+ nsRect transformedBounds = nsLayoutUtils::TransformFrameRectToAncestor(
+ frame, frameBounds, aContainerFrame);
+
+ if (aHaveRect) {
+ // We can't use nsRect::UnionRect since it drops empty rects on
+ // the floor, and we need to include them. (Thus we need
+ // aHaveRect to know when to drop the initial value on the floor.)
+ aRect = aRect.UnionEdges(transformedBounds);
+ } else {
+ aHaveRect = true;
+ aRect = transformedBounds;
+ }
+}
+
+static bool ComputeNeedToScroll(WhenToScroll aWhenToScroll, nscoord aLineSize,
+ nscoord aRectMin, nscoord aRectMax,
+ nscoord aViewMin, nscoord aViewMax) {
+ // See how the rect should be positioned in a given axis.
+ switch (aWhenToScroll) {
+ case WhenToScroll::Always:
+ // The caller wants the frame as visible as possible
+ return true;
+ case WhenToScroll::IfNotVisible:
+ if (aLineSize > (aRectMax - aRectMin)) {
+ // If the line size is greater than the size of the rect
+ // to scroll into view, do not use the line size to determine
+ // if we need to scroll.
+ aLineSize = 0;
+ }
+
+ // Scroll only if no part of the frame is visible in this view.
+ return aRectMax - aLineSize <= aViewMin ||
+ aRectMin + aLineSize >= aViewMax;
+ case WhenToScroll::IfNotFullyVisible:
+ // Scroll only if part of the frame is hidden and more can fit in view
+ return !(aRectMin >= aViewMin && aRectMax <= aViewMax) &&
+ std::min(aViewMax, aRectMax) - std::max(aRectMin, aViewMin) <
+ aViewMax - aViewMin;
+ }
+ return false;
+}
+
+static nscoord ComputeWhereToScroll(WhereToScroll aWhereToScroll,
+ nscoord aOriginalCoord, nscoord aRectMin,
+ nscoord aRectMax, nscoord aViewMin,
+ nscoord aViewMax, nscoord* aRangeMin,
+ nscoord* aRangeMax) {
+ nscoord resultCoord = aOriginalCoord;
+ nscoord scrollPortLength = aViewMax - aViewMin;
+ if (!aWhereToScroll.mPercentage) {
+ // Scroll the minimum amount necessary to show as much as possible of the
+ // frame. If the frame is too large, don't hide any initially visible part
+ // of it.
+ nscoord min = std::min(aRectMin, aRectMax - scrollPortLength);
+ nscoord max = std::max(aRectMin, aRectMax - scrollPortLength);
+ resultCoord = std::min(std::max(aOriginalCoord, min), max);
+ } else {
+ float percent = aWhereToScroll.mPercentage.value() / 100.0f;
+ nscoord frameAlignCoord =
+ NSToCoordRound(aRectMin + (aRectMax - aRectMin) * percent);
+ resultCoord = NSToCoordRound(frameAlignCoord - scrollPortLength * percent);
+ }
+ // Force the scroll range to extend to include resultCoord.
+ *aRangeMin = std::min(resultCoord, aRectMax - scrollPortLength);
+ *aRangeMax = std::max(resultCoord, aRectMin);
+ return resultCoord;
+}
+
+static WhereToScroll GetApplicableWhereToScroll(
+ const nsIScrollableFrame* aFrameAsScrollable,
+ const nsIFrame* aScrollableFrame, const nsIFrame* aTarget,
+ ScrollDirection aScrollDirection, WhereToScroll aOriginal) {
+ MOZ_ASSERT(do_QueryFrame(aFrameAsScrollable) == aScrollableFrame);
+ if (aTarget == aScrollableFrame) {
+ return aOriginal;
+ }
+
+ StyleScrollSnapAlignKeyword align =
+ aScrollDirection == ScrollDirection::eHorizontal
+ ? aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).first
+ : aFrameAsScrollable->GetScrollSnapAlignFor(aTarget).second;
+
+ switch (align) {
+ case StyleScrollSnapAlignKeyword::None:
+ return aOriginal;
+ case StyleScrollSnapAlignKeyword::Start:
+ return WhereToScroll::Start;
+ case StyleScrollSnapAlignKeyword::Center:
+ return WhereToScroll::Center;
+ case StyleScrollSnapAlignKeyword::End:
+ return WhereToScroll::End;
+ }
+ return aOriginal;
+}
+
+/**
+ * This function takes a scrollable frame, a rect in the coordinate system
+ * of the scrolled frame, and a desired percentage-based scroll
+ * position and attempts to scroll the rect to that position in the
+ * visual viewport.
+ *
+ * This needs to work even if aRect has a width or height of zero.
+ */
+static void ScrollToShowRect(nsIScrollableFrame* aFrameAsScrollable,
+ const nsIFrame* aScrollableFrame,
+ const nsIFrame* aTarget, const nsRect& aRect,
+ const Sides aScrollPaddingSkipSides,
+ const nsMargin& aMargin, ScrollAxis aVertical,
+ ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
+ nsPoint scrollPt = aFrameAsScrollable->GetVisualViewportOffset();
+ const nsPoint originalScrollPt = scrollPt;
+ const nsRect visibleRect(scrollPt,
+ aFrameAsScrollable->GetVisualViewportSize());
+
+ const nsMargin padding = [&] {
+ nsMargin p = aFrameAsScrollable->GetScrollPadding();
+ p.ApplySkipSides(aScrollPaddingSkipSides);
+ return p + aMargin;
+ }();
+
+ const nsRect rectToScrollIntoView = [&] {
+ nsRect r(aRect);
+ r.Inflate(padding);
+ return r.Intersect(aFrameAsScrollable->GetScrolledRect());
+ }();
+
+ nsSize lineSize;
+ // Don't call GetLineScrollAmount unless we actually need it. Not only
+ // does this save time, but it's not safe to call GetLineScrollAmount
+ // during reflow (because it depends on font size inflation and doesn't
+ // use the in-reflow-safe font-size inflation path). If we did call it,
+ // it would assert and possible give the wrong result.
+ if (aVertical.mWhenToScroll == WhenToScroll::IfNotVisible ||
+ aHorizontal.mWhenToScroll == WhenToScroll::IfNotVisible) {
+ lineSize = aFrameAsScrollable->GetLineScrollAmount();
+ }
+ ScrollStyles ss = aFrameAsScrollable->GetScrollStyles();
+ nsRect allowedRange(scrollPt, nsSize(0, 0));
+ ScrollDirections directions =
+ aFrameAsScrollable->GetAvailableScrollingDirections();
+
+ if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
+ ss.mVertical != StyleOverflow::Hidden) &&
+ (!aVertical.mOnlyIfPerceivedScrollableDirection ||
+ (directions.contains(ScrollDirection::eVertical)))) {
+ if (ComputeNeedToScroll(aVertical.mWhenToScroll, lineSize.height, aRect.y,
+ aRect.YMost(), visibleRect.y + padding.top,
+ visibleRect.YMost() - padding.bottom)) {
+ // If the scroll-snap-align on the frame is valid, we need to respect it.
+ WhereToScroll whereToScroll = GetApplicableWhereToScroll(
+ aFrameAsScrollable, aScrollableFrame, aTarget,
+ ScrollDirection::eVertical, aVertical.mWhereToScroll);
+
+ nscoord maxHeight;
+ scrollPt.y = ComputeWhereToScroll(
+ whereToScroll, scrollPt.y, rectToScrollIntoView.y,
+ rectToScrollIntoView.YMost(), visibleRect.y, visibleRect.YMost(),
+ &allowedRange.y, &maxHeight);
+ allowedRange.height = maxHeight - allowedRange.y;
+ }
+ }
+
+ if (((aScrollFlags & ScrollFlags::ScrollOverflowHidden) ||
+ ss.mHorizontal != StyleOverflow::Hidden) &&
+ (!aHorizontal.mOnlyIfPerceivedScrollableDirection ||
+ (directions.contains(ScrollDirection::eHorizontal)))) {
+ if (ComputeNeedToScroll(aHorizontal.mWhenToScroll, lineSize.width, aRect.x,
+ aRect.XMost(), visibleRect.x + padding.left,
+ visibleRect.XMost() - padding.right)) {
+ // If the scroll-snap-align on the frame is valid, we need to respect it.
+ WhereToScroll whereToScroll = GetApplicableWhereToScroll(
+ aFrameAsScrollable, aScrollableFrame, aTarget,
+ ScrollDirection::eHorizontal, aHorizontal.mWhereToScroll);
+
+ nscoord maxWidth;
+ scrollPt.x = ComputeWhereToScroll(
+ whereToScroll, scrollPt.x, rectToScrollIntoView.x,
+ rectToScrollIntoView.XMost(), visibleRect.x, visibleRect.XMost(),
+ &allowedRange.x, &maxWidth);
+ allowedRange.width = maxWidth - allowedRange.x;
+ }
+ }
+
+ // If we don't need to scroll, then don't try since it might cancel
+ // a current smooth scroll operation.
+ if (scrollPt == originalScrollPt) {
+ return;
+ }
+
+ ScrollMode scrollMode = ScrollMode::Instant;
+ // Default to an instant scroll, but if the scroll behavior given is "auto"
+ // or "smooth", use that as the specified behavior. If the user has disabled
+ // smooth scrolls, a given mode of "auto" or "smooth" should not result in
+ // a smooth scroll.
+ ScrollBehavior behavior = ScrollBehavior::Instant;
+ if (aScrollFlags & ScrollFlags::ScrollSmooth) {
+ behavior = ScrollBehavior::Smooth;
+ } else if (aScrollFlags & ScrollFlags::ScrollSmoothAuto) {
+ behavior = ScrollBehavior::Auto;
+ }
+ bool smoothScroll = aFrameAsScrollable->IsSmoothScroll(behavior);
+ if (smoothScroll) {
+ scrollMode = ScrollMode::SmoothMsd;
+ }
+ nsIFrame* frame = do_QueryFrame(aFrameAsScrollable);
+ AutoWeakFrame weakFrame(frame);
+ aFrameAsScrollable->ScrollTo(scrollPt, scrollMode, &allowedRange,
+ ScrollSnapFlags::IntendedEndPosition,
+ aScrollFlags & ScrollFlags::TriggeredByScript
+ ? ScrollTriggeredByScript::Yes
+ : ScrollTriggeredByScript::No);
+ if (!weakFrame.IsAlive()) {
+ return;
+ }
+
+ // If this is the RCD-RSF, also call ScrollToVisual() since we want to
+ // scroll the rect into view visually, and that may require scrolling
+ // the visual viewport in scenarios where there is not enough layout
+ // scroll range.
+ if (aFrameAsScrollable->IsRootScrollFrameOfDocument() &&
+ frame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ frame->PresShell()->ScrollToVisual(scrollPt, FrameMetrics::eMainThread,
+ scrollMode);
+ }
+}
+
+nsresult PresShell::ScrollContentIntoView(nsIContent* aContent,
+ ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ ScrollFlags aScrollFlags) {
+ NS_ENSURE_TRUE(aContent, NS_ERROR_NULL_POINTER);
+ RefPtr<Document> composedDoc = aContent->GetComposedDoc();
+ NS_ENSURE_STATE(composedDoc);
+
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+
+ if (mContentToScrollTo) {
+ mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
+ }
+ mContentToScrollTo = aContent;
+ ScrollIntoViewData* data = new ScrollIntoViewData();
+ data->mContentScrollVAxis = aVertical;
+ data->mContentScrollHAxis = aHorizontal;
+ data->mContentToScrollToFlags = aScrollFlags;
+ if (NS_FAILED(mContentToScrollTo->SetProperty(
+ nsGkAtoms::scrolling, data,
+ nsINode::DeleteProperty<PresShell::ScrollIntoViewData>))) {
+ mContentToScrollTo = nullptr;
+ }
+
+ // If the target frame has an ancestor of a `content-visibility: auto`
+ // element ensure that it is laid out, so that the boundary rectangle is
+ // correct.
+ // Additionally, ensure that all ancestor elements with 'content-visibility:
+ // auto' are set to 'visible'. so that they are laid out as visible before
+ // scrolling, improving the accuracy of the scroll position, especially when
+ // the scroll target is within the overflow area. And here invoking
+ // 'SetTemporarilyVisibleForScrolledIntoViewDescendant' would make the
+ // intersection observer knows that it should generate entries for these
+ // c-v:auto ancestors, so that the content relevancy could be checked again
+ // after scrolling. https://drafts.csswg.org/css-contain-2/#cv-notes
+ bool reflowedForHiddenContent = false;
+ if (mContentToScrollTo) {
+ if (nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame()) {
+ bool hasContentVisibilityAutoAncestor = false;
+ auto* ancestor = frame->GetClosestContentVisibilityAncestor(
+ nsIFrame::IncludeContentVisibility::Auto);
+ while (ancestor) {
+ if (auto* element = Element::FromNodeOrNull(ancestor->GetContent())) {
+ hasContentVisibilityAutoAncestor = true;
+ element->SetTemporarilyVisibleForScrolledIntoViewDescendant(true);
+ element->SetVisibleForContentVisibility(true);
+ }
+ ancestor = ancestor->GetClosestContentVisibilityAncestor(
+ nsIFrame::IncludeContentVisibility::Auto);
+ }
+ if (hasContentVisibilityAutoAncestor) {
+ UpdateHiddenContentInForcedLayout(frame);
+ // TODO: There might be the other already scheduled relevancy updates,
+ // other than caused be scrollIntoView.
+ UpdateContentRelevancyImmediately(ContentRelevancyReason::Visible);
+ reflowedForHiddenContent = ReflowForHiddenContentIfNeeded();
+ }
+ }
+ }
+
+ if (!reflowedForHiddenContent) {
+ // Flush layout and attempt to scroll in the process.
+ if (PresShell* presShell = composedDoc->GetPresShell()) {
+ presShell->SetNeedLayoutFlush();
+ }
+ composedDoc->FlushPendingNotifications(FlushType::InterruptibleLayout);
+ }
+
+ // If mContentToScrollTo is non-null, that means we interrupted the reflow
+ // (or suppressed it altogether because we're suppressing interruptible
+ // flushes right now) and won't necessarily get the position correct, but do
+ // a best-effort scroll here. The other option would be to do this inside
+ // FlushPendingNotifications, but I'm not sure the repeated scrolling that
+ // could trigger if reflows keep getting interrupted would be more desirable
+ // than a single best-effort scroll followed by one final scroll on the first
+ // completed reflow.
+ if (mContentToScrollTo) {
+ DoScrollContentIntoView();
+ }
+ return NS_OK;
+}
+
+static nsMargin GetScrollMargin(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ // If we're focusing something that can't be targeted by content, allow
+ // content to customize the margin.
+ //
+ // TODO: This is also a bit of an issue for delegated focus, see
+ // https://github.com/whatwg/html/issues/7033.
+ if (aFrame->GetContent() && aFrame->GetContent()->ChromeOnlyAccess()) {
+ if (const nsIContent* userContent =
+ aFrame->GetContent()->GetChromeOnlyAccessSubtreeRootParent()) {
+ if (const nsIFrame* frame = userContent->GetPrimaryFrame()) {
+ return frame->StyleMargin()->GetScrollMargin();
+ }
+ }
+ }
+ return aFrame->StyleMargin()->GetScrollMargin();
+}
+
+void PresShell::DoScrollContentIntoView() {
+ NS_ASSERTION(mDidInitialize, "should have done initial reflow by now");
+
+ nsIFrame* frame = mContentToScrollTo->GetPrimaryFrame();
+
+ if (!frame || frame->IsHiddenByContentVisibilityOnAnyAncestor(
+ nsIFrame::IncludeContentVisibility::Hidden)) {
+ mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ return;
+ }
+
+ if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // The reflow flush before this scroll got interrupted, and this frame's
+ // coords and size are all zero, and it has no content showing anyway.
+ // Don't bother scrolling to it. We'll try again when we finish up layout.
+ return;
+ }
+
+ auto* data = static_cast<ScrollIntoViewData*>(
+ mContentToScrollTo->GetProperty(nsGkAtoms::scrolling));
+ if (MOZ_UNLIKELY(!data)) {
+ mContentToScrollTo = nullptr;
+ return;
+ }
+
+ ScrollFrameIntoView(frame, Nothing(), data->mContentScrollVAxis,
+ data->mContentScrollHAxis, data->mContentToScrollToFlags);
+}
+
+bool PresShell::ScrollFrameIntoView(
+ nsIFrame* aTargetFrame, const Maybe<nsRect>& aKnownRectRelativeToTarget,
+ ScrollAxis aVertical, ScrollAxis aHorizontal, ScrollFlags aScrollFlags) {
+ // The scroll margin only applies to the whole bounds of the element, so don't
+ // apply it if we get an arbitrary rect / point to scroll to.
+ const nsMargin scrollMargin =
+ aKnownRectRelativeToTarget ? nsMargin() : GetScrollMargin(aTargetFrame);
+
+ Sides skipPaddingSides;
+ const auto MaybeSkipPaddingSides = [&](nsIFrame* aFrame) {
+ if (!aFrame->IsStickyPositioned()) {
+ return;
+ }
+ const nsPoint pos = aFrame->GetPosition();
+ const nsPoint normalPos = aFrame->GetNormalPosition();
+ if (pos == normalPos) {
+ return; // Frame is not stuck.
+ }
+ // If we're targetting a sticky element, make sure not to apply
+ // scroll-padding on the direction we're stuck.
+ const auto& offsets = aFrame->StylePosition()->mOffset;
+ for (auto side : AllPhysicalSides()) {
+ if (offsets.Get(side).IsAuto()) {
+ continue;
+ }
+ // See if this axis is stuck.
+ const bool yAxis = side == eSideTop || side == eSideBottom;
+ const bool stuck = yAxis ? pos.y != normalPos.y : pos.x != normalPos.x;
+ if (!stuck) {
+ continue;
+ }
+ skipPaddingSides |= SideToSideBit(side);
+ }
+ };
+
+ nsIFrame* container = aTargetFrame;
+
+ // This function needs to work even if rect has a width or height of 0.
+ nsRect rect = [&] {
+ if (aKnownRectRelativeToTarget) {
+ return *aKnownRectRelativeToTarget;
+ }
+ MaybeSkipPaddingSides(aTargetFrame);
+ while (nsIFrame* parent = container->GetParent()) {
+ container = parent;
+ if (static_cast<nsIScrollableFrame*>(do_QueryFrame(container))) {
+ // We really just need a non-fragmented frame so that we can accumulate
+ // the bounds of all our continuations relative to it. We shouldn't jump
+ // out of our nearest scrollable frame, and that's an ok reference
+ // frame, so try to use that, or the root frame if there's nothing to
+ // scroll in this document.
+ break;
+ }
+ MaybeSkipPaddingSides(container);
+ }
+ MOZ_DIAGNOSTIC_ASSERT(container);
+
+ nsRect targetFrameBounds;
+ {
+ bool haveRect = false;
+ const bool useWholeLineHeightForInlines =
+ aVertical.mWhenToScroll != WhenToScroll::IfNotFullyVisible;
+ AutoAssertNoDomMutations
+ guard; // Ensure use of nsILineIterators is safe.
+ nsIFrame* prevBlock = nullptr;
+ // Reuse the same line iterator across calls to AccumulateFrameBounds.
+ // We set it every time we detect a new block (stored in prevBlock).
+ nsILineIterator* lines = nullptr;
+ // The last line we found a continuation on in |lines|. We assume that
+ // later continuations cannot come on earlier lines.
+ int32_t curLine = 0;
+ nsIFrame* frame = aTargetFrame;
+ do {
+ AccumulateFrameBounds(container, frame, useWholeLineHeightForInlines,
+ targetFrameBounds, haveRect, prevBlock, lines,
+ curLine);
+ } while ((frame = frame->GetNextContinuation()));
+ }
+
+ return targetFrameBounds;
+ }();
+
+ bool didScroll = false;
+ const nsIFrame* target = aTargetFrame;
+ // Walk up the frame hierarchy scrolling the rect into view and
+ // keeping rect relative to container
+ do {
+ if (nsIScrollableFrame* sf = do_QueryFrame(container)) {
+ nsPoint oldPosition = sf->GetScrollPosition();
+ nsRect targetRect = rect;
+ // Inflate the scrolled rect by the container's padding in each dimension,
+ // unless we have 'overflow-clip-box-*: content-box' in that dimension.
+ auto* disp = container->StyleDisplay();
+ if (disp->mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
+ disp->mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
+ WritingMode wm = container->GetWritingMode();
+ bool cbH = (wm.IsVertical() ? disp->mOverflowClipBoxBlock
+ : disp->mOverflowClipBoxInline) ==
+ StyleOverflowClipBox::ContentBox;
+ bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
+ : disp->mOverflowClipBoxBlock) ==
+ StyleOverflowClipBox::ContentBox;
+ nsMargin padding = container->GetUsedPadding();
+ if (!cbH) {
+ padding.left = padding.right = nscoord(0);
+ }
+ if (!cbV) {
+ padding.top = padding.bottom = nscoord(0);
+ }
+ targetRect.Inflate(padding);
+ }
+
+ targetRect -= sf->GetScrolledFrame()->GetPosition();
+
+ {
+ AutoWeakFrame wf(container);
+ ScrollToShowRect(sf, container, target, targetRect, skipPaddingSides,
+ scrollMargin, aVertical, aHorizontal, aScrollFlags);
+ if (!wf.IsAlive()) {
+ return didScroll;
+ }
+ }
+
+ nsPoint newPosition = sf->LastScrollDestination();
+ // If the scroll position increased, that means our content moved up,
+ // so our rect's offset should decrease
+ rect += oldPosition - newPosition;
+
+ if (oldPosition != newPosition) {
+ didScroll = true;
+ }
+
+ // only scroll one container when this flag is set
+ if (aScrollFlags & ScrollFlags::ScrollFirstAncestorOnly) {
+ break;
+ }
+
+ // This scroll container will be the next target element in the nearest
+ // ancestor scroll container.
+ target = container;
+ // We found a sticky scroll container, we shouldn't skip that side
+ // anymore.
+ skipPaddingSides = {};
+ }
+
+ MaybeSkipPaddingSides(container);
+
+ nsIFrame* parent;
+ if (container->IsTransformed()) {
+ container->GetTransformMatrix(ViewportType::Layout, RelativeTo{nullptr},
+ &parent);
+ rect =
+ nsLayoutUtils::TransformFrameRectToAncestor(container, rect, parent);
+ } else {
+ rect += container->GetPosition();
+ parent = container->GetParent();
+ }
+ if (!parent && !(aScrollFlags & ScrollFlags::ScrollNoParentFrames)) {
+ nsPoint extraOffset(0, 0);
+ int32_t APD = container->PresContext()->AppUnitsPerDevPixel();
+ parent = nsLayoutUtils::GetCrossDocParentFrameInProcess(container,
+ &extraOffset);
+ if (parent) {
+ int32_t parentAPD = parent->PresContext()->AppUnitsPerDevPixel();
+ rect = rect.ScaleToOtherAppUnitsRoundOut(APD, parentAPD);
+ rect += extraOffset;
+ } else {
+ nsCOMPtr<nsIDocShell> docShell =
+ container->PresContext()->GetDocShell();
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(docShell)) {
+ // Defer to the parent document if this is an out-of-process iframe.
+ Unused << browserChild->SendScrollRectIntoView(
+ rect, aVertical, aHorizontal, aScrollFlags, APD);
+ }
+ }
+ }
+ container = parent;
+ } while (container);
+
+ return didScroll;
+}
+
+void PresShell::ScheduleViewManagerFlush() {
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return;
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->RefreshDriver()->ScheduleViewManagerFlush();
+ }
+ SetNeedLayoutFlush();
+}
+
+void PresShell::DispatchSynthMouseMove(WidgetGUIEvent* aEvent) {
+ AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint", "DispatchSynthMouseMove",
+ GRAPHICS, mPresContext->GetDocShell());
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsView* targetView = nsView::GetViewFor(aEvent->mWidget);
+ if (!targetView) return;
+ RefPtr<nsViewManager> viewManager = targetView->GetViewManager();
+ viewManager->DispatchEvent(aEvent, targetView, &status);
+}
+
+void PresShell::ClearMouseCaptureOnView(nsView* aView) {
+ if (nsIContent* capturingContent = GetCapturingContent()) {
+ if (aView) {
+ // if a view was specified, ensure that the captured content is within
+ // this view.
+ nsIFrame* frame = capturingContent->GetPrimaryFrame();
+ if (frame) {
+ nsView* view = frame->GetClosestView();
+ // if there is no view, capturing won't be handled any more, so
+ // just release the capture.
+ if (view) {
+ do {
+ if (view == aView) {
+ ReleaseCapturingContent();
+ // the view containing the captured content likely disappeared so
+ // disable capture for now.
+ AllowMouseCapture(false);
+ break;
+ }
+
+ view = view->GetParent();
+ } while (view);
+ // return if the view wasn't found
+ return;
+ }
+ }
+ }
+
+ ReleaseCapturingContent();
+ }
+
+ // disable mouse capture until the next mousedown as a dialog has opened
+ // or a drag has started. Otherwise, someone could start capture during
+ // the modal dialog or drag.
+ AllowMouseCapture(false);
+}
+
+void PresShell::ClearMouseCapture() {
+ ReleaseCapturingContent();
+ AllowMouseCapture(false);
+}
+
+void PresShell::ClearMouseCapture(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+
+ nsIContent* capturingContent = GetCapturingContent();
+ if (!capturingContent) {
+ return;
+ }
+
+ nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
+ const bool shouldClear =
+ !capturingFrame ||
+ nsLayoutUtils::IsAncestorFrameCrossDocInProcess(aFrame, capturingFrame);
+ if (shouldClear) {
+ ClearMouseCapture();
+ }
+}
+
+nsresult PresShell::CaptureHistoryState(nsILayoutHistoryState** aState) {
+ MOZ_ASSERT(nullptr != aState, "null state pointer");
+
+ // We actually have to mess with the docshell here, since we want to
+ // store the state back in it.
+ // XXXbz this isn't really right, since this is being called in the
+ // content viewer's Hide() method... by that point the docshell's
+ // state could be wrong. We should sort out a better ownership
+ // model for the layout history state.
+ nsCOMPtr<nsIDocShell> docShell(mPresContext->GetDocShell());
+ if (!docShell) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsILayoutHistoryState> historyState;
+ docShell->GetLayoutHistoryState(getter_AddRefs(historyState));
+ if (!historyState) {
+ // Create the document state object
+ historyState = NS_NewLayoutHistoryState();
+ docShell->SetLayoutHistoryState(historyState);
+ }
+
+ *aState = historyState;
+ NS_IF_ADDREF(*aState);
+
+ // Capture frame state for the entire frame hierarchy
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) return NS_OK;
+
+ mFrameConstructor->CaptureFrameState(rootFrame, historyState);
+
+ return NS_OK;
+}
+
+void PresShell::ScheduleBeforeFirstPaint() {
+ if (!mDocument->IsResourceDoc()) {
+ // Notify observers that a new page is about to be drawn. Execute this
+ // as soon as it is safe to run JS, which is guaranteed to be before we
+ // go back to the event loop and actually draw the page.
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("PresShell::ScheduleBeforeFirstPaint this=%p", this));
+
+ nsContentUtils::AddScriptRunner(
+ new nsBeforeFirstPaintDispatcher(mDocument));
+ }
+}
+
+void PresShell::UnsuppressAndInvalidate() {
+ // Note: We ignore the EnsureVisible check for resource documents, because
+ // they won't have a docshell, so they'll always fail EnsureVisible.
+ if ((!mDocument->IsResourceDoc() && !mPresContext->EnsureVisible()) ||
+ mHaveShutDown) {
+ // No point; we're about to be torn down anyway.
+ return;
+ }
+
+ ScheduleBeforeFirstPaint();
+
+ PROFILER_MARKER_UNTYPED("UnsuppressAndInvalidate", GRAPHICS);
+
+ mPaintingSuppressed = false;
+ if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
+ // let's assume that outline on a root frame is not supported
+ rootFrame->InvalidateFrame();
+ }
+
+ if (mPresContext->IsRootContentDocumentCrossProcess()) {
+ if (auto* bc = BrowserChild::GetFrom(mDocument->GetDocShell())) {
+ if (mDocument->IsInitialDocument()) {
+ bc->SendDidUnsuppressPaintingNormalPriority();
+ } else {
+ bc->SendDidUnsuppressPainting();
+ }
+ }
+ }
+
+ // now that painting is unsuppressed, focus may be set on the document
+ if (nsPIDOMWindowOuter* win = mDocument->GetWindow()) {
+ win->SetReadyForFocus();
+ }
+
+ if (!mHaveShutDown) {
+ SynthesizeMouseMove(false);
+ ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+}
+
+void PresShell::CancelPaintSuppressionTimer() {
+ if (mPaintSuppressionTimer) {
+ mPaintSuppressionTimer->Cancel();
+ mPaintSuppressionTimer = nullptr;
+ }
+}
+
+void PresShell::UnsuppressPainting() {
+ CancelPaintSuppressionTimer();
+
+ if (mIsDocumentGone || !mPaintingSuppressed) {
+ return;
+ }
+
+ // If we have reflows pending, just wait until we process
+ // the reflows and get all the frames where we want them
+ // before actually unlocking the painting. Otherwise
+ // go ahead and unlock now.
+ if (!mDirtyRoots.IsEmpty())
+ mShouldUnsuppressPainting = true;
+ else
+ UnsuppressAndInvalidate();
+}
+
+// Post a request to handle an arbitrary callback after reflow has finished.
+nsresult PresShell::PostReflowCallback(nsIReflowCallback* aCallback) {
+ void* result = AllocateByObjectID(eArenaObjectID_nsCallbackEventRequest,
+ sizeof(nsCallbackEventRequest));
+ nsCallbackEventRequest* request = (nsCallbackEventRequest*)result;
+
+ request->callback = aCallback;
+ request->next = nullptr;
+
+ if (mLastCallbackEventRequest) {
+ mLastCallbackEventRequest = mLastCallbackEventRequest->next = request;
+ } else {
+ mFirstCallbackEventRequest = request;
+ mLastCallbackEventRequest = request;
+ }
+
+ return NS_OK;
+}
+
+void PresShell::CancelReflowCallback(nsIReflowCallback* aCallback) {
+ nsCallbackEventRequest* before = nullptr;
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ while (node) {
+ nsIReflowCallback* callback = node->callback;
+
+ if (callback == aCallback) {
+ nsCallbackEventRequest* toFree = node;
+ if (node == mFirstCallbackEventRequest) {
+ node = node->next;
+ mFirstCallbackEventRequest = node;
+ NS_ASSERTION(before == nullptr, "impossible");
+ } else {
+ node = node->next;
+ before->next = node;
+ }
+
+ if (toFree == mLastCallbackEventRequest) {
+ mLastCallbackEventRequest = before;
+ }
+
+ FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, toFree);
+ } else {
+ before = node;
+ node = node->next;
+ }
+ }
+}
+
+void PresShell::CancelPostedReflowCallbacks() {
+ while (mFirstCallbackEventRequest) {
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ mFirstCallbackEventRequest = node->next;
+ if (!mFirstCallbackEventRequest) {
+ mLastCallbackEventRequest = nullptr;
+ }
+ nsIReflowCallback* callback = node->callback;
+ FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
+ if (callback) {
+ callback->ReflowCallbackCanceled();
+ }
+ }
+}
+
+void PresShell::HandlePostedReflowCallbacks(bool aInterruptible) {
+ while (true) {
+ // Call all our callbacks, tell us if we need to flush again.
+ bool shouldFlush = false;
+ while (mFirstCallbackEventRequest) {
+ nsCallbackEventRequest* node = mFirstCallbackEventRequest;
+ mFirstCallbackEventRequest = node->next;
+ if (!mFirstCallbackEventRequest) {
+ mLastCallbackEventRequest = nullptr;
+ }
+ nsIReflowCallback* callback = node->callback;
+ FreeByObjectID(eArenaObjectID_nsCallbackEventRequest, node);
+ if (callback && callback->ReflowFinished()) {
+ shouldFlush = true;
+ }
+ }
+
+ if (!shouldFlush || mIsDestroying) {
+ return;
+ }
+
+ // The flush might cause us to have more callbacks.
+ const auto flushType =
+ aInterruptible ? FlushType::InterruptibleLayout : FlushType::Layout;
+ FlushPendingNotifications(flushType);
+ }
+}
+
+bool PresShell::IsSafeToFlush() const {
+ // Not safe if we are getting torn down, reflowing, or in the middle of frame
+ // construction.
+ if (mIsReflowing || mChangeNestCount || mIsDestroying) {
+ return false;
+ }
+
+ // Not safe if we are painting
+ if (nsViewManager* viewManager = GetViewManager()) {
+ bool isPainting = false;
+ viewManager->IsPainting(isPainting);
+ if (isPainting) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void PresShell::NotifyFontFaceSetOnRefresh() {
+ if (FontFaceSet* set = mDocument->GetFonts()) {
+ set->DidRefresh();
+ }
+}
+
+void PresShell::DoFlushPendingNotifications(FlushType aType) {
+ // by default, flush animations if aType >= FlushType::Style
+ mozilla::ChangesToFlush flush(aType, aType >= FlushType::Style);
+ FlushPendingNotifications(flush);
+}
+
+#ifdef DEBUG
+static void AssertFrameSubtreeIsSane(const nsIFrame& aRoot) {
+ if (const nsIContent* content = aRoot.GetContent()) {
+ MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle(),
+ "Node not in the flattened tree still has a frame?");
+ }
+
+ for (const auto& childList : aRoot.ChildLists()) {
+ for (const nsIFrame* child : childList.mList) {
+ AssertFrameSubtreeIsSane(*child);
+ }
+ }
+}
+#endif
+
+static inline void AssertFrameTreeIsSane(const PresShell& aPresShell) {
+#ifdef DEBUG
+ if (const nsIFrame* root = aPresShell.GetRootFrame()) {
+ AssertFrameSubtreeIsSane(*root);
+ }
+#endif
+}
+
+static void TriggerPendingScrollTimelineAnimations(Document* aDocument) {
+ auto* tracker = aDocument->GetScrollTimelineAnimationTracker();
+ if (!tracker || !tracker->HasPendingAnimations()) {
+ return;
+ }
+ tracker->TriggerPendingAnimations();
+}
+
+void PresShell::DoFlushPendingNotifications(mozilla::ChangesToFlush aFlush) {
+ // FIXME(emilio, bug 1530177): Turn into a release assert when bug 1530188 and
+ // bug 1530190 are fixed.
+ MOZ_DIAGNOSTIC_ASSERT(!mForbiddenToFlush, "This is bad!");
+
+ // Per our API contract, hold a strong ref to ourselves until we return.
+ RefPtr<PresShell> kungFuDeathGrip = this;
+
+ /**
+ * VERY IMPORTANT: If you add some sort of new flushing to this
+ * method, make sure to add the relevant SetNeedLayoutFlush or
+ * SetNeedStyleFlush calls on the shell.
+ */
+ FlushType flushType = aFlush.mFlushType;
+
+ // If this is a layout flush, first update the relevancy of any content
+ // of elements with `content-visibility: auto` so that the values
+ // returned from script queries are up-to-date.
+ if (flushType >= mozilla::FlushType::Layout) {
+ UpdateRelevancyOfContentVisibilityAutoFrames();
+ }
+
+ MOZ_ASSERT(NeedFlush(flushType), "Why did we get called?");
+
+ AUTO_PROFILER_MARKER_TEXT(
+ "DoFlushPendingNotifications", LAYOUT,
+ MarkerOptions(MarkerStack::Capture(), MarkerInnerWindowIdFromDocShell(
+ mPresContext->GetDocShell())),
+ nsDependentCString(kFlushTypeNames[flushType]));
+ AUTO_PROFILER_LABEL_DYNAMIC_CSTR_NONSENSITIVE(
+ "PresShell::DoFlushPendingNotifications", LAYOUT,
+ kFlushTypeNames[flushType]);
+
+#ifdef ACCESSIBILITY
+# ifdef DEBUG
+ if (nsAccessibilityService* accService = GetAccService()) {
+ NS_ASSERTION(!accService->IsProcessingRefreshDriverNotification(),
+ "Flush during accessible tree update!");
+ }
+# endif
+#endif
+
+ NS_ASSERTION(flushType >= FlushType::Style, "Why did we get called?");
+
+ mNeedStyleFlush = false;
+ mNeedThrottledAnimationFlush =
+ mNeedThrottledAnimationFlush && !aFlush.mFlushAnimations;
+ mNeedLayoutFlush =
+ mNeedLayoutFlush && (flushType < FlushType::InterruptibleLayout);
+
+ bool isSafeToFlush = IsSafeToFlush();
+
+ // If layout could possibly trigger scripts, then it's only safe to flush if
+ // it's safe to run script.
+ bool hasHadScriptObject;
+ if (mDocument->GetScriptHandlingObject(hasHadScriptObject) ||
+ hasHadScriptObject) {
+ isSafeToFlush = isSafeToFlush && nsContentUtils::IsSafeToRunScript();
+ }
+
+ // Don't flush if the doc is already in the bfcache.
+ if (MOZ_UNLIKELY(mDocument->GetPresShell() != this)) {
+ MOZ_DIAGNOSTIC_ASSERT(!mDocument->GetPresShell(),
+ "Where did this shell come from?");
+ isSafeToFlush = false;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(!mIsDestroying || !isSafeToFlush);
+ MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mViewManager);
+ MOZ_DIAGNOSTIC_ASSERT(mIsDestroying || mDocument->HasShellOrBFCacheEntry());
+
+ // Make sure the view manager stays alive.
+ RefPtr<nsViewManager> viewManager = mViewManager;
+ bool didStyleFlush = false;
+ bool didLayoutFlush = false;
+ if (isSafeToFlush) {
+ // Record that we are in a flush, so that our optimization in
+ // Document::FlushPendingNotifications doesn't skip any re-entrant
+ // calls to us. Otherwise, we might miss some needed flushes, since
+ // we clear mNeedStyleFlush / mNeedLayoutFlush here at the top of
+ // the function but we might not have done the work yet.
+ AutoRestore<bool> guard(mInFlush);
+ mInFlush = true;
+
+ // We need to make sure external resource documents are flushed too (for
+ // example, svg filters that reference a filter in an external document
+ // need the frames in the external document to be constructed for the
+ // filter to work). We only need external resources to be flushed when the
+ // main document is flushing >= FlushType::Frames, so we flush external
+ // resources here instead of Document::FlushPendingNotifications.
+ mDocument->FlushExternalResources(flushType);
+
+ // Force flushing of any pending content notifications that might have
+ // queued up while our event was pending. That will ensure that we don't
+ // construct frames for content right now that's still waiting to be
+ // notified on,
+ mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
+
+ mDocument->UpdateSVGUseElementShadowTrees();
+
+ // Process pending restyles, since any flush of the presshell wants
+ // up-to-date style data.
+ if (MOZ_LIKELY(!mIsDestroying)) {
+ viewManager->FlushDelayedResize();
+ mPresContext->FlushPendingMediaFeatureValuesChanged();
+ }
+
+ if (MOZ_LIKELY(!mIsDestroying)) {
+ // Now that we have flushed media queries, update the rules before looking
+ // up @font-face / @counter-style / @font-feature-values rules.
+ StyleSet()->UpdateStylistIfNeeded();
+
+ // Flush any pending update of the user font set, since that could
+ // cause style changes (for updating ex/ch units, and to cause a
+ // reflow).
+ mDocument->FlushUserFontSet();
+
+ mPresContext->FlushCounterStyles();
+
+ mPresContext->FlushFontFeatureValues();
+
+ mPresContext->FlushFontPaletteValues();
+
+ // Flush any requested SMIL samples.
+ if (mDocument->HasAnimationController()) {
+ mDocument->GetAnimationController()->FlushResampleRequests();
+ }
+ }
+
+ // The FlushResampleRequests() above flushed style changes.
+ if (MOZ_LIKELY(!mIsDestroying) && aFlush.mFlushAnimations &&
+ mPresContext->EffectCompositor()) {
+ mPresContext->EffectCompositor()->PostRestyleForThrottledAnimations();
+ }
+
+ // The FlushResampleRequests() above flushed style changes.
+ if (MOZ_LIKELY(!mIsDestroying)) {
+ nsAutoScriptBlocker scriptBlocker;
+ Maybe<uint64_t> innerWindowID;
+ if (auto* window = mDocument->GetInnerWindow()) {
+ innerWindowID = Some(window->WindowID());
+ }
+ AutoProfilerStyleMarker tracingStyleFlush(std::move(mStyleCause),
+ innerWindowID);
+ PerfStats::AutoMetricRecording<PerfStats::Metric::Styling> autoRecording;
+ LAYOUT_TELEMETRY_RECORD_BASE(Restyle);
+
+ mPresContext->RestyleManager()->ProcessPendingRestyles();
+ mNeedStyleFlush = false;
+ }
+
+ AssertFrameTreeIsSane(*this);
+
+ didStyleFlush = true;
+
+ // There might be more pending constructors now, but we're not going to
+ // worry about them. They can't be triggered during reflow, so we should
+ // be good.
+
+ if (flushType >= (SuppressInterruptibleReflows()
+ ? FlushType::Layout
+ : FlushType::InterruptibleLayout) &&
+ !mIsDestroying) {
+ didLayoutFlush = true;
+ if (DoFlushLayout(/* aInterruptible = */ flushType < FlushType::Layout)) {
+ if (mContentToScrollTo) {
+ DoScrollContentIntoView();
+ if (mContentToScrollTo) {
+ mContentToScrollTo->RemoveProperty(nsGkAtoms::scrolling);
+ mContentToScrollTo = nullptr;
+ }
+ }
+ }
+ }
+
+ FlushPendingScrollResnap();
+
+ if (MOZ_LIKELY(!mIsDestroying)) {
+ // Try to trigger pending scroll-driven animations after we flush
+ // style and layout (if any). If we try to trigger them after flushing
+ // style but the frame tree is not ready, we will check them again after
+ // we flush layout because the requirement to trigger scroll-driven
+ // animations is that the associated scroll containers are ready (i.e. the
+ // scroll-timeline is active), and this depends on the readiness of the
+ // scrollable frame and the primary frame of the scroll container.
+ TriggerPendingScrollTimelineAnimations(mDocument);
+ }
+
+ if (flushType >= FlushType::Layout) {
+ if (!mIsDestroying) {
+ viewManager->UpdateWidgetGeometry();
+ }
+ }
+ }
+
+ if (!didStyleFlush && flushType >= FlushType::Style && !mIsDestroying) {
+ SetNeedStyleFlush();
+ if (aFlush.mFlushAnimations) {
+ SetNeedThrottledAnimationFlush();
+ }
+ }
+
+ if (!didLayoutFlush && flushType >= FlushType::InterruptibleLayout &&
+ !mIsDestroying) {
+ // We suppressed this flush either due to it not being safe to flush,
+ // or due to SuppressInterruptibleReflows(). Either way, the
+ // mNeedLayoutFlush flag needs to be re-set.
+ SetNeedLayoutFlush();
+ }
+
+ // Update flush counters
+ if (didStyleFlush) {
+ mLayoutTelemetry.IncReqsPerFlush(FlushType::Style);
+ }
+
+ if (didLayoutFlush) {
+ mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
+ }
+
+ // Record telemetry for the number of requests per each flush type.
+ //
+ // Flushes happen as style or style+layout. This depends upon the `flushType`
+ // where flushType >= InterruptibleLayout means flush layout and flushType >=
+ // Style means flush style. We only report if didLayoutFlush or didStyleFlush
+ // is true because we care if a flush really did take place. (Flush is guarded
+ // by `isSafeToFlush == true`.)
+ if (flushType >= FlushType::InterruptibleLayout && didLayoutFlush) {
+ MOZ_ASSERT(didLayoutFlush == didStyleFlush);
+ mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Layout);
+ } else if (flushType >= FlushType::Style && didStyleFlush) {
+ MOZ_ASSERT(!didLayoutFlush);
+ mLayoutTelemetry.PingReqsPerFlushTelemetry(FlushType::Style);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected CharacterDataChanged");
+ MOZ_ASSERT(aContent->OwnerDoc() == mDocument, "Unexpected document");
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ mPresContext->RestyleManager()->CharacterDataChanged(aContent, aInfo);
+ mFrameConstructor->CharacterDataChanged(aContent, aInfo);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ElementStateChanged(
+ Document* aDocument, Element* aElement, ElementState aStateMask) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentStateChanged");
+ MOZ_ASSERT(aDocument == mDocument, "Unexpected aDocument");
+
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->ElementStateChanged(aElement, aStateMask);
+ }
+}
+
+void PresShell::DocumentStatesChanged(DocumentState aStateMask) {
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected DocumentStatesChanged");
+ MOZ_ASSERT(mDocument);
+ MOZ_ASSERT(!aStateMask.IsEmpty());
+
+ if (mDidInitialize) {
+ StyleSet()->InvalidateStyleForDocumentStateChanges(aStateMask);
+ }
+
+ if (aStateMask.HasState(DocumentState::WINDOW_INACTIVE)) {
+ if (nsIFrame* root = mFrameConstructor->GetRootFrame()) {
+ root->SchedulePaint();
+ }
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeWillChange(
+ Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeWillChange");
+ MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
+
+ // XXXwaterson it might be more elegant to wait until after the
+ // initial reflow to begin observing the document. That would
+ // squelch any other inappropriate notifications as well.
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->AttributeWillChange(aElement, aNameSpaceID,
+ aAttribute, aModType);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::AttributeChanged(
+ Element* aElement, int32_t aNameSpaceID, nsAtom* aAttribute,
+ int32_t aModType, const nsAttrValue* aOldValue) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected AttributeChanged");
+ MOZ_ASSERT(aElement->OwnerDoc() == mDocument, "Unexpected document");
+
+ // XXXwaterson it might be more elegant to wait until after the
+ // initial reflow to begin observing the document. That would
+ // squelch any other inappropriate notifications as well.
+ if (mDidInitialize) {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ mPresContext->RestyleManager()->AttributeChanged(
+ aElement, aNameSpaceID, aAttribute, aModType, aOldValue);
+ }
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentAppended(
+ nsIContent* aFirstNewContent) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentAppended");
+ MOZ_ASSERT(aFirstNewContent->OwnerDoc() == mDocument, "Unexpected document");
+
+ // We never call ContentAppended with a document as the container, so we can
+ // assert that we have an nsIContent parent.
+ MOZ_ASSERT(aFirstNewContent->GetParent());
+ MOZ_ASSERT(aFirstNewContent->GetParent()->IsElement() ||
+ aFirstNewContent->GetParent()->IsShadowRoot());
+
+ if (!mDidInitialize) {
+ return;
+ }
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ mPresContext->RestyleManager()->ContentAppended(aFirstNewContent);
+
+ mFrameConstructor->ContentAppended(
+ aFirstNewContent, nsCSSFrameConstructor::InsertionKind::Async);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentInserted(
+ nsIContent* aChild) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentInserted");
+ MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
+
+ if (!mDidInitialize) {
+ return;
+ }
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ mPresContext->RestyleManager()->ContentInserted(aChild);
+
+ mFrameConstructor->ContentInserted(
+ aChild, nsCSSFrameConstructor::InsertionKind::Async);
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY void PresShell::ContentRemoved(
+ nsIContent* aChild, nsIContent* aPreviousSibling) {
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript());
+ MOZ_ASSERT(!mIsDocumentGone, "Unexpected ContentRemoved");
+ MOZ_ASSERT(aChild->OwnerDoc() == mDocument, "Unexpected document");
+ nsINode* container = aChild->GetParentNode();
+
+ // Notify the ESM that the content has been removed, so that
+ // it can clean up any state related to the content.
+
+ mPresContext->EventStateManager()->ContentRemoved(mDocument, aChild);
+
+ nsAutoCauseReflowNotifier crNotifier(this);
+
+ // Call this here so it only happens for real content mutations and
+ // not cases when the frame constructor calls its own methods to force
+ // frame reconstruction.
+ nsIContent* oldNextSibling = nullptr;
+
+ // Editor calls into here with NAC via HTMLEditor::DeleteRefToAnonymousNode.
+ // This could be asserted if that caller is fixed.
+ if (MOZ_LIKELY(!aChild->IsRootOfNativeAnonymousSubtree())) {
+ oldNextSibling = aPreviousSibling ? aPreviousSibling->GetNextSibling()
+ : container->GetFirstChild();
+ }
+
+ // After removing aChild from tree we should save information about live
+ // ancestor
+ if (mPointerEventTarget &&
+ mPointerEventTarget->IsInclusiveDescendantOf(aChild)) {
+ mPointerEventTarget = aChild->GetParent();
+ }
+
+ mFrameConstructor->ContentRemoved(aChild, oldNextSibling,
+ nsCSSFrameConstructor::REMOVE_CONTENT);
+
+ // NOTE(emilio): It's important that this goes after the frame constructor
+ // stuff, otherwise the frame constructor can't see elements which are
+ // display: contents / display: none, because we'd have cleared all the style
+ // data from there.
+ mPresContext->RestyleManager()->ContentRemoved(aChild, oldNextSibling);
+}
+
+void PresShell::NotifyCounterStylesAreDirty() {
+ // TODO: Looks like that nsFrameConstructor::NotifyCounterStylesAreDirty()
+ // does not run script. If so, we don't need to block script with
+ // nsAutoCauseReflowNotifier here. Instead, there should be methods
+ // and stack only class which manages only mChangeNestCount for
+ // avoiding unnecessary `MOZ_CAN_RUN_SCRIPT` marking.
+ nsAutoCauseReflowNotifier reflowNotifier(this);
+ mFrameConstructor->NotifyCounterStylesAreDirty();
+}
+
+bool PresShell::FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const {
+ return mDirtyRoots.FrameIsAncestorOfAnyElement(aFrame);
+}
+
+void PresShell::ReconstructFrames() {
+ MOZ_ASSERT(!mFrameConstructor->GetRootFrame() || mDidInitialize,
+ "Must not have root frame before initial reflow");
+ if (!mDidInitialize || mIsDestroying) {
+ // Nothing to do here
+ return;
+ }
+
+ if (Element* root = mDocument->GetRootElement()) {
+ PostRecreateFramesFor(root);
+ }
+
+ mDocument->FlushPendingNotifications(FlushType::Frames);
+}
+
+nsresult PresShell::RenderDocument(const nsRect& aRect,
+ RenderDocumentFlags aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aThebesContext) {
+ NS_ENSURE_TRUE(!(aFlags & RenderDocumentFlags::IsUntrusted),
+ NS_ERROR_NOT_IMPLEMENTED);
+
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext) {
+ rootPresContext->FlushWillPaintObservers();
+ if (mIsDestroying) return NS_OK;
+ }
+
+ nsAutoScriptBlocker blockScripts;
+
+ // Set up the rectangle as the path in aThebesContext
+ gfxRect r(0, 0, nsPresContext::AppUnitsToFloatCSSPixels(aRect.width),
+ nsPresContext::AppUnitsToFloatCSSPixels(aRect.height));
+ aThebesContext->NewPath();
+#ifdef MOZ_GFX_OPTIMIZE_MOBILE
+ aThebesContext->SnappedRectangle(r);
+#else
+ aThebesContext->Rectangle(r);
+#endif
+
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) {
+ // Nothing to paint, just fill the rect
+ aThebesContext->SetColor(sRGBColor::FromABGR(aBackgroundColor));
+ aThebesContext->Fill();
+ return NS_OK;
+ }
+
+ gfxContextAutoSaveRestore save(aThebesContext);
+
+ MOZ_ASSERT(aThebesContext->CurrentOp() == CompositionOp::OP_OVER);
+
+ aThebesContext->Clip();
+
+ nsDeviceContext* devCtx = mPresContext->DeviceContext();
+
+ gfxPoint offset(-nsPresContext::AppUnitsToFloatCSSPixels(aRect.x),
+ -nsPresContext::AppUnitsToFloatCSSPixels(aRect.y));
+ gfxFloat scale =
+ gfxFloat(devCtx->AppUnitsPerDevPixel()) / AppUnitsPerCSSPixel();
+
+ // Since canvas APIs use floats to set up their matrices, we may have some
+ // slight rounding errors here. We use NudgeToIntegers() here to adjust
+ // matrix components that are integers up to the accuracy of floats to be
+ // those integers.
+ gfxMatrix newTM = aThebesContext->CurrentMatrixDouble()
+ .PreTranslate(offset)
+ .PreScale(scale, scale)
+ .NudgeToIntegers();
+ aThebesContext->SetMatrixDouble(newTM);
+
+ AutoSaveRestoreRenderingState _(this);
+
+ bool wouldFlushRetainedLayers = false;
+ PaintFrameFlags flags = PaintFrameFlags::IgnoreSuppression;
+ if (aThebesContext->CurrentMatrix().HasNonIntegerTranslation()) {
+ flags |= PaintFrameFlags::InTransform;
+ }
+ if (!(aFlags & RenderDocumentFlags::AsyncDecodeImages)) {
+ flags |= PaintFrameFlags::SyncDecodeImages;
+ }
+ if (aFlags & RenderDocumentFlags::UseHighQualityScaling) {
+ flags |= PaintFrameFlags::UseHighQualityScaling;
+ }
+ if (aFlags & RenderDocumentFlags::UseWidgetLayers) {
+ // We only support using widget layers on display root's with widgets.
+ nsView* view = rootFrame->GetView();
+ if (view && view->GetWidget() &&
+ nsLayoutUtils::GetDisplayRootFrame(rootFrame) == rootFrame) {
+ WindowRenderer* renderer = view->GetWidget()->GetWindowRenderer();
+ // WebRenderLayerManagers in content processes
+ // don't support taking snapshots.
+ if (renderer &&
+ (!renderer->AsKnowsCompositor() || XRE_IsParentProcess())) {
+ flags |= PaintFrameFlags::WidgetLayers;
+ }
+ }
+ }
+ if (!(aFlags & RenderDocumentFlags::DrawCaret)) {
+ wouldFlushRetainedLayers = true;
+ flags |= PaintFrameFlags::HideCaret;
+ }
+ if (aFlags & RenderDocumentFlags::IgnoreViewportScrolling) {
+ wouldFlushRetainedLayers = !IgnoringViewportScrolling();
+ mRenderingStateFlags |= RenderingStateFlags::IgnoringViewportScrolling;
+ }
+ if (aFlags & RenderDocumentFlags::ResetViewportScrolling) {
+ wouldFlushRetainedLayers = true;
+ flags |= PaintFrameFlags::ResetViewportScrolling;
+ }
+ if (aFlags & RenderDocumentFlags::DrawWindowNotFlushing) {
+ mRenderingStateFlags |= RenderingStateFlags::DrawWindowNotFlushing;
+ }
+ if (aFlags & RenderDocumentFlags::DocumentRelative) {
+ // XXX be smarter about this ... drawWindow might want a rect
+ // that's "pretty close" to what our retained layer tree covers.
+ // In that case, it wouldn't disturb normal rendering too much,
+ // and we should allow it.
+ wouldFlushRetainedLayers = true;
+ flags |= PaintFrameFlags::DocumentRelative;
+ }
+
+ // Don't let drawWindow blow away our retained layer tree
+ if ((flags & PaintFrameFlags::WidgetLayers) && wouldFlushRetainedLayers) {
+ flags &= ~PaintFrameFlags::WidgetLayers;
+ }
+
+ nsLayoutUtils::PaintFrame(aThebesContext, rootFrame, nsRegion(aRect),
+ aBackgroundColor,
+ nsDisplayListBuilderMode::Painting, flags);
+
+ return NS_OK;
+}
+
+/*
+ * Clip the display list aList to a range. Returns the clipped
+ * rectangle surrounding the range.
+ */
+nsRect PresShell::ClipListToRange(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsRange* aRange) {
+ // iterate though the display items and add up the bounding boxes of each.
+ // This will allow the total area of the frames within the range to be
+ // determined. To do this, remove an item from the bottom of the list, check
+ // whether it should be part of the range, and if so, append it to the top
+ // of the temporary list tmpList. If the item is a text frame at the end of
+ // the selection range, clip it to the portion of the text frame that is
+ // part of the selection. Then, append the wrapper to the top of the list.
+ // Otherwise, just delete the item and don't append it.
+ nsRect surfaceRect;
+
+ for (nsDisplayItem* i : aList->TakeItems()) {
+ if (i->GetType() == DisplayItemType::TYPE_CONTAINER) {
+ aList->AppendToTop(i);
+ surfaceRect.UnionRect(
+ surfaceRect, ClipListToRange(aBuilder, i->GetChildren(), aRange));
+ continue;
+ }
+
+ // itemToInsert indiciates the item that should be inserted into the
+ // temporary list. If null, no item should be inserted.
+ nsDisplayItem* itemToInsert = nullptr;
+ nsIFrame* frame = i->Frame();
+ nsIContent* content = frame->GetContent();
+ if (content) {
+ bool atStart = (content == aRange->GetStartContainer());
+ bool atEnd = (content == aRange->GetEndContainer());
+ if ((atStart || atEnd) && frame->IsTextFrame()) {
+ auto [frameStartOffset, frameEndOffset] = frame->GetOffsets();
+
+ int32_t hilightStart =
+ atStart ? std::max(static_cast<int32_t>(aRange->StartOffset()),
+ frameStartOffset)
+ : frameStartOffset;
+ int32_t hilightEnd =
+ atEnd ? std::min(static_cast<int32_t>(aRange->EndOffset()),
+ frameEndOffset)
+ : frameEndOffset;
+ if (hilightStart < hilightEnd) {
+ // determine the location of the start and end edges of the range.
+ nsPoint startPoint, endPoint;
+ frame->GetPointFromOffset(hilightStart, &startPoint);
+ frame->GetPointFromOffset(hilightEnd, &endPoint);
+
+ // The clip rectangle is determined by taking the the start and
+ // end points of the range, offset from the reference frame.
+ // Because of rtl, the end point may be to the left of (or above,
+ // in vertical mode) the start point, so x (or y) is set to the
+ // lower of the values.
+ nsRect textRect(aBuilder->ToReferenceFrame(frame), frame->GetSize());
+ if (frame->GetWritingMode().IsVertical()) {
+ nscoord y = std::min(startPoint.y, endPoint.y);
+ textRect.y += y;
+ textRect.height = std::max(startPoint.y, endPoint.y) - y;
+ } else {
+ nscoord x = std::min(startPoint.x, endPoint.x);
+ textRect.x += x;
+ textRect.width = std::max(startPoint.x, endPoint.x) - x;
+ }
+ surfaceRect.UnionRect(surfaceRect, textRect);
+
+ const ActiveScrolledRoot* asr = i->GetActiveScrolledRoot();
+
+ DisplayItemClip newClip;
+ newClip.SetTo(textRect);
+
+ const DisplayItemClipChain* newClipChain =
+ aBuilder->AllocateDisplayItemClipChain(newClip, asr, nullptr);
+
+ i->IntersectClip(aBuilder, newClipChain, true);
+ itemToInsert = i;
+ }
+ }
+ // Don't try to descend into subdocuments.
+ // If this ever changes we'd need to add handling for subdocuments with
+ // different zoom levels.
+ else if (content->GetUncomposedDoc() ==
+ aRange->GetStartContainer()->GetUncomposedDoc()) {
+ // if the node is within the range, append it to the temporary list
+ bool before, after;
+ nsresult rv =
+ RangeUtils::CompareNodeToRange(content, aRange, &before, &after);
+ if (NS_SUCCEEDED(rv) && !before && !after) {
+ itemToInsert = i;
+ bool snap;
+ surfaceRect.UnionRect(surfaceRect, i->GetBounds(aBuilder, &snap));
+ }
+ }
+ }
+
+ // insert the item into the list if necessary. If the item has a child
+ // list, insert that as well
+ nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
+ if (itemToInsert || sublist) {
+ aList->AppendToTop(itemToInsert ? itemToInsert : i);
+ // if the item is a list, iterate over it as well
+ if (sublist)
+ surfaceRect.UnionRect(surfaceRect,
+ ClipListToRange(aBuilder, sublist, aRange));
+ } else {
+ // otherwise, just delete the item and don't readd it to the list
+ i->Destroy(aBuilder);
+ }
+ }
+
+ return surfaceRect;
+}
+
+#ifdef DEBUG
+# include <stdio.h>
+
+static bool gDumpRangePaintList = false;
+#endif
+
+UniquePtr<RangePaintInfo> PresShell::CreateRangePaintInfo(
+ nsRange* aRange, nsRect& aSurfaceRect, bool aForPrimarySelection) {
+ nsIFrame* ancestorFrame = nullptr;
+ nsIFrame* rootFrame = GetRootFrame();
+
+ // If the start or end of the range is the document, just use the root
+ // frame, otherwise get the common ancestor of the two endpoints of the
+ // range.
+ nsINode* startContainer = aRange->GetStartContainer();
+ nsINode* endContainer = aRange->GetEndContainer();
+ Document* doc = startContainer->GetComposedDoc();
+ if (startContainer == doc || endContainer == doc) {
+ ancestorFrame = rootFrame;
+ } else {
+ nsINode* ancestor = nsContentUtils::GetClosestCommonInclusiveAncestor(
+ startContainer, endContainer);
+ NS_ASSERTION(!ancestor || ancestor->IsContent(),
+ "common ancestor is not content");
+
+ while (ancestor && ancestor->IsContent()) {
+ ancestorFrame = ancestor->AsContent()->GetPrimaryFrame();
+ if (ancestorFrame) {
+ break;
+ }
+
+ ancestor = ancestor->GetParentOrShadowHostNode();
+ }
+
+ // use the nearest ancestor frame that includes all continuations as the
+ // root for building the display list
+ while (ancestorFrame &&
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(ancestorFrame))
+ ancestorFrame = ancestorFrame->GetParent();
+ }
+
+ if (!ancestorFrame) {
+ return nullptr;
+ }
+
+ // get a display list containing the range
+ auto info = MakeUnique<RangePaintInfo>(aRange, ancestorFrame);
+ info->mBuilder.SetIncludeAllOutOfFlows();
+ if (aForPrimarySelection) {
+ info->mBuilder.SetSelectedFramesOnly();
+ }
+ info->mBuilder.EnterPresShell(ancestorFrame);
+
+ ContentSubtreeIterator subtreeIter;
+ nsresult rv = subtreeIter.Init(aRange);
+ if (NS_FAILED(rv)) {
+ return nullptr;
+ }
+
+ auto BuildDisplayListForNode = [&](nsINode* aNode) {
+ if (MOZ_UNLIKELY(!aNode->IsContent())) {
+ return;
+ }
+ nsIFrame* frame = aNode->AsContent()->GetPrimaryFrame();
+ // XXX deal with frame being null due to display:contents
+ for (; frame;
+ frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
+ info->mBuilder.SetVisibleRect(frame->InkOverflowRect());
+ info->mBuilder.SetDirtyRect(frame->InkOverflowRect());
+ frame->BuildDisplayListForStackingContext(&info->mBuilder, &info->mList);
+ }
+ };
+ if (startContainer->NodeType() == nsINode::TEXT_NODE) {
+ BuildDisplayListForNode(startContainer);
+ }
+ for (; !subtreeIter.IsDone(); subtreeIter.Next()) {
+ nsCOMPtr<nsINode> node = subtreeIter.GetCurrentNode();
+ BuildDisplayListForNode(node);
+ }
+ if (endContainer != startContainer &&
+ endContainer->NodeType() == nsINode::TEXT_NODE) {
+ BuildDisplayListForNode(endContainer);
+ }
+
+ // If one of the ancestor presShells (including this one) has a resolution
+ // set, we may have some APZ zoom applied. That means we may want to rasterize
+ // the nodes at that zoom level. Populate `info` with the relevant information
+ // so that the caller can decide what to do. Also wrap the display list in
+ // appropriate nsDisplayAsyncZoom display items. This code handles the general
+ // case with nested async zooms (even though that never actually happens),
+ // because it fell out of the implementation for free.
+ //
+ // TODO: Do we need to do the same for ancestor transforms?
+ for (nsPresContext* ctx = GetPresContext(); ctx;
+ ctx = ctx->GetParentPresContext()) {
+ PresShell* shell = ctx->PresShell();
+ float resolution = shell->GetResolution();
+
+ // If we are at the root document in the process, try to see if documents
+ // in enclosing processes have a resolution and include that as well.
+ if (!ctx->GetParentPresContext()) {
+ // xScale is an arbitrary choice. Outside of edge cases involving CSS
+ // transforms, xScale == yScale so it doesn't matter.
+ resolution *= ViewportUtils::TryInferEnclosingResolution(shell).xScale;
+ }
+
+ if (resolution == 1.0) {
+ continue;
+ }
+
+ info->mResolution *= resolution;
+ nsIFrame* rootScrollFrame = shell->GetRootScrollFrame();
+ ViewID zoomedId =
+ nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
+
+ nsDisplayList wrapped(&info->mBuilder);
+ wrapped.AppendNewToTop<nsDisplayAsyncZoom>(&info->mBuilder, rootScrollFrame,
+ &info->mList, nullptr, zoomedId);
+ info->mList.AppendToTop(&wrapped);
+ }
+
+#ifdef DEBUG
+ if (gDumpRangePaintList) {
+ fprintf(stderr, "CreateRangePaintInfo --- before ClipListToRange:\n");
+ nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
+ }
+#endif
+
+ nsRect rangeRect = ClipListToRange(&info->mBuilder, &info->mList, aRange);
+
+ info->mBuilder.LeavePresShell(ancestorFrame, &info->mList);
+
+#ifdef DEBUG
+ if (gDumpRangePaintList) {
+ fprintf(stderr, "CreateRangePaintInfo --- after ClipListToRange:\n");
+ nsIFrame::PrintDisplayList(&(info->mBuilder), info->mList);
+ }
+#endif
+
+ // determine the offset of the reference frame for the display list
+ // to the root frame. This will allow the coordinates used when painting
+ // to all be offset from the same point
+ info->mRootOffset = ancestorFrame->GetBoundingClientRect().TopLeft();
+ rangeRect.MoveBy(info->mRootOffset);
+ aSurfaceRect.UnionRect(aSurfaceRect, rangeRect);
+
+ return info;
+}
+
+already_AddRefed<SourceSurface> PresShell::PaintRangePaintInfo(
+ const nsTArray<UniquePtr<RangePaintInfo>>& aItems, Selection* aSelection,
+ const Maybe<CSSIntRegion>& aRegion, nsRect aArea,
+ const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
+ RenderImageFlags aFlags) {
+ nsPresContext* pc = GetPresContext();
+ if (!pc || aArea.width == 0 || aArea.height == 0) return nullptr;
+
+ // use the rectangle to create the surface
+ LayoutDeviceIntRect pixelArea = LayoutDeviceIntRect::FromAppUnitsToOutside(
+ aArea, pc->AppUnitsPerDevPixel());
+
+ // if the image should not be resized, scale must be 1
+ float scale = 1.0;
+
+ nsRect maxSize;
+ pc->DeviceContext()->GetClientRect(maxSize);
+
+ // check if the image should be resized
+ bool resize = !!(aFlags & RenderImageFlags::AutoScale);
+
+ if (resize) {
+ // check if image-resizing-algorithm should be used
+ if (aFlags & RenderImageFlags::IsImage) {
+ // get max screensize
+ int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width);
+ int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height);
+ // resize image relative to the screensize
+ // get best height/width relative to screensize
+ float bestHeight = float(maxHeight) * RELATIVE_SCALEFACTOR;
+ float bestWidth = float(maxWidth) * RELATIVE_SCALEFACTOR;
+ // calculate scale for bestWidth
+ float adjustedScale = bestWidth / float(pixelArea.width);
+ // get the worst height (height when width is perfect)
+ float worstHeight = float(pixelArea.height) * adjustedScale;
+ // get the difference of best and worst height
+ float difference = bestHeight - worstHeight;
+ // halve the difference and add it to worstHeight to get
+ // the best compromise between bestHeight and bestWidth,
+ // then calculate the corresponding scale factor
+ adjustedScale = (worstHeight + difference / 2) / float(pixelArea.height);
+ // prevent upscaling
+ scale = std::min(scale, adjustedScale);
+ } else {
+ // get half of max screensize
+ int32_t maxWidth = pc->AppUnitsToDevPixels(maxSize.width >> 1);
+ int32_t maxHeight = pc->AppUnitsToDevPixels(maxSize.height >> 1);
+ if (pixelArea.width > maxWidth || pixelArea.height > maxHeight) {
+ // divide the maximum size by the image size in both directions.
+ // Whichever direction produces the smallest result determines how much
+ // should be scaled.
+ if (pixelArea.width > maxWidth)
+ scale = std::min(scale, float(maxWidth) / pixelArea.width);
+ if (pixelArea.height > maxHeight)
+ scale = std::min(scale, float(maxHeight) / pixelArea.height);
+ }
+ }
+
+ // Pick a resolution scale factor that is the highest we need for any of
+ // the items. This means some items may get rendered at a higher-than-needed
+ // resolution but at least nothing will be avoidably blurry.
+ float resolutionScale = 1.0;
+ for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
+ resolutionScale = std::max(resolutionScale, rangeInfo->mResolution);
+ }
+ float unclampedResolution = resolutionScale;
+ // Clamp the resolution scale so that `pixelArea` when scaled by `scale` and
+ // `resolutionScale` isn't bigger than `maxSize`. This prevents creating
+ // giant/unbounded images.
+ resolutionScale =
+ std::min(resolutionScale, maxSize.width / (scale * pixelArea.width));
+ resolutionScale =
+ std::min(resolutionScale, maxSize.height / (scale * pixelArea.height));
+ // The following assert should only get hit if pixelArea scaled by `scale`
+ // alone would already have been bigger than `maxSize`, which should never
+ // be the case. For release builds we handle gracefully by reverting
+ // resolutionScale to 1.0 to avoid unexpected consequences.
+ MOZ_ASSERT(resolutionScale >= 1.0);
+ resolutionScale = std::max(1.0f, resolutionScale);
+
+ scale *= resolutionScale;
+
+ // Now we need adjust the output screen position of the surface based on the
+ // scaling factor and any APZ zoom that may be in effect. The goal is here
+ // to set `aScreenRect`'s top-left corner (in screen-relative LD pixels)
+ // such that the scaling effect on the surface appears anchored at `aPoint`
+ // ("anchor" here is like "transform-origin"). When this code is e.g. used
+ // to generate a drag image for dragging operations, `aPoint` refers to the
+ // position of the mouse cursor (also in screen-relative LD pixels), and the
+ // user-visible effect of doing this is that the point at which the user
+ // clicked to start the drag remains under the mouse during the drag.
+
+ // In order to do this we first compute the top-left corner of the
+ // pixelArea is screen-relative LD pixels.
+ LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
+ LayoutDevicePoint(pixelArea.TopLeft()), pc);
+ // And then adjust the output screen position based on that, which we can do
+ // since everything here is screen-relative LD pixels. Note that the scale
+ // factor we use here is the effective "transform" scale applied to the
+ // content we're painting, relative to the scale at which it would normally
+ // get painted at as part of page rendering (`unclampedResolution`).
+ float scaleRelativeToNormalContent = scale / unclampedResolution;
+ aScreenRect->x =
+ NSToIntFloor(aPoint.x - float(aPoint.x.value - visualPoint.x.value) *
+ scaleRelativeToNormalContent);
+ aScreenRect->y =
+ NSToIntFloor(aPoint.y - float(aPoint.y.value - visualPoint.y.value) *
+ scaleRelativeToNormalContent);
+
+ pixelArea.width = NSToIntFloor(float(pixelArea.width) * scale);
+ pixelArea.height = NSToIntFloor(float(pixelArea.height) * scale);
+ if (!pixelArea.width || !pixelArea.height) {
+ return nullptr;
+ }
+ } else {
+ // move aScreenRect to the position of the surface in screen coordinates
+ LayoutDevicePoint visualPoint = ViewportUtils::ToScreenRelativeVisual(
+ LayoutDevicePoint(pixelArea.TopLeft()), pc);
+ aScreenRect->MoveTo(RoundedToInt(visualPoint));
+ }
+ aScreenRect->width = pixelArea.width;
+ aScreenRect->height = pixelArea.height;
+
+ RefPtr<DrawTarget> dt =
+ gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
+ IntSize(pixelArea.width, pixelArea.height), SurfaceFormat::B8G8R8A8);
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ gfxContext ctx(dt);
+
+ if (aRegion) {
+ RefPtr<PathBuilder> builder = dt->CreatePathBuilder(FillRule::FILL_WINDING);
+
+ // Convert aRegion from CSS pixels to dev pixels
+ nsIntRegion region = aRegion->ToAppUnits(AppUnitsPerCSSPixel())
+ .ToOutsidePixels(pc->AppUnitsPerDevPixel());
+ for (auto iter = region.RectIter(); !iter.Done(); iter.Next()) {
+ const IntRect& rect = iter.Get();
+
+ builder->MoveTo(rect.TopLeft());
+ builder->LineTo(rect.TopRight());
+ builder->LineTo(rect.BottomRight());
+ builder->LineTo(rect.BottomLeft());
+ builder->LineTo(rect.TopLeft());
+ }
+
+ RefPtr<Path> path = builder->Finish();
+ ctx.Clip(path);
+ }
+
+ gfxMatrix initialTM = ctx.CurrentMatrixDouble();
+
+ if (resize) {
+ initialTM.PreScale(scale, scale);
+ }
+
+ // translate so that points are relative to the surface area
+ gfxPoint surfaceOffset = nsLayoutUtils::PointToGfxPoint(
+ -aArea.TopLeft(), pc->AppUnitsPerDevPixel());
+ initialTM.PreTranslate(surfaceOffset);
+
+ // temporarily hide the selection so that text is drawn normally. If a
+ // selection is being rendered, use that, otherwise use the presshell's
+ // selection.
+ RefPtr<nsFrameSelection> frameSelection;
+ if (aSelection) {
+ frameSelection = aSelection->GetFrameSelection();
+ } else {
+ frameSelection = FrameSelection();
+ }
+ int16_t oldDisplaySelection = frameSelection->GetDisplaySelection();
+ frameSelection->SetDisplaySelection(nsISelectionController::SELECTION_HIDDEN);
+
+ // next, paint each range in the selection
+ for (const UniquePtr<RangePaintInfo>& rangeInfo : aItems) {
+ // the display lists paint relative to the offset from the reference
+ // frame, so account for that translation too:
+ gfxPoint rootOffset = nsLayoutUtils::PointToGfxPoint(
+ rangeInfo->mRootOffset, pc->AppUnitsPerDevPixel());
+ ctx.SetMatrixDouble(initialTM.PreTranslate(rootOffset));
+ aArea.MoveBy(-rangeInfo->mRootOffset.x, -rangeInfo->mRootOffset.y);
+ nsRegion visible(aArea);
+ rangeInfo->mList.PaintRoot(&rangeInfo->mBuilder, &ctx,
+ nsDisplayList::PAINT_DEFAULT, Nothing());
+ aArea.MoveBy(rangeInfo->mRootOffset.x, rangeInfo->mRootOffset.y);
+ }
+
+ // restore the old selection display state
+ frameSelection->SetDisplaySelection(oldDisplaySelection);
+
+ return dt->Snapshot();
+}
+
+already_AddRefed<SourceSurface> PresShell::RenderNode(
+ nsINode* aNode, const Maybe<CSSIntRegion>& aRegion,
+ const LayoutDeviceIntPoint aPoint, LayoutDeviceIntRect* aScreenRect,
+ RenderImageFlags aFlags) {
+ // area will hold the size of the surface needed to draw the node, measured
+ // from the root frame.
+ nsRect area;
+ nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
+
+ // nothing to draw if the node isn't in a document
+ if (!aNode->IsInComposedDoc()) {
+ return nullptr;
+ }
+
+ RefPtr<nsRange> range = nsRange::Create(aNode);
+ IgnoredErrorResult rv;
+ range->SelectNode(*aNode, rv);
+ if (rv.Failed()) {
+ return nullptr;
+ }
+
+ UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, false);
+ if (info) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier, or change the return type to void.
+ rangeItems.AppendElement(std::move(info));
+ }
+
+ Maybe<CSSIntRegion> region = aRegion;
+ if (region) {
+ // combine the area with the supplied region
+ CSSIntRect rrectPixels = region->GetBounds();
+
+ nsRect rrect = ToAppUnits(rrectPixels, AppUnitsPerCSSPixel());
+ area.IntersectRect(area, rrect);
+
+ nsPresContext* pc = GetPresContext();
+ if (!pc) return nullptr;
+
+ // move the region so that it is offset from the topleft corner of the
+ // surface
+ region->MoveBy(-nsPresContext::AppUnitsToIntCSSPixels(area.x),
+ -nsPresContext::AppUnitsToIntCSSPixels(area.y));
+ }
+
+ return PaintRangePaintInfo(rangeItems, nullptr, region, area, aPoint,
+ aScreenRect, aFlags);
+}
+
+already_AddRefed<SourceSurface> PresShell::RenderSelection(
+ Selection* aSelection, const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags) {
+ // area will hold the size of the surface needed to draw the selection,
+ // measured from the root frame.
+ nsRect area;
+ nsTArray<UniquePtr<RangePaintInfo>> rangeItems;
+
+ // iterate over each range and collect them into the rangeItems array.
+ // This is done so that the size of selection can be determined so as
+ // to allocate a surface area
+ const uint32_t rangeCount = aSelection->RangeCount();
+ NS_ASSERTION(rangeCount > 0, "RenderSelection called with no selection");
+ for (const uint32_t r : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(aSelection->RangeCount() == rangeCount);
+ RefPtr<nsRange> range = aSelection->GetRangeAt(r);
+
+ UniquePtr<RangePaintInfo> info = CreateRangePaintInfo(range, area, true);
+ if (info) {
+ // XXX(Bug 1631371) Check if this should use a fallible operation as it
+ // pretended earlier.
+ rangeItems.AppendElement(std::move(info));
+ }
+ }
+
+ return PaintRangePaintInfo(rangeItems, aSelection, Nothing(), area, aPoint,
+ aScreenRect, aFlags);
+}
+
+void AddDisplayItemToBottom(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsDisplayItem* aItem) {
+ if (!aItem) {
+ return;
+ }
+
+ nsDisplayList list(aBuilder);
+ list.AppendToTop(aItem);
+ list.AppendToTop(aList);
+ aList->AppendToTop(&list);
+}
+
+static bool AddCanvasBackgroundColor(const nsDisplayList* aList,
+ nsIFrame* aCanvasFrame, nscolor aColor,
+ bool aCSSBackgroundColor) {
+ for (nsDisplayItem* i : *aList) {
+ const DisplayItemType type = i->GetType();
+
+ if (i->Frame() == aCanvasFrame &&
+ type == DisplayItemType::TYPE_CANVAS_BACKGROUND_COLOR) {
+ auto* bg = static_cast<nsDisplayCanvasBackgroundColor*>(i);
+ bg->SetExtraBackgroundColor(aColor);
+ return true;
+ }
+
+ const bool isBlendContainer =
+ type == DisplayItemType::TYPE_BLEND_CONTAINER ||
+ type == DisplayItemType::TYPE_TABLE_BLEND_CONTAINER;
+
+ nsDisplayList* sublist = i->GetSameCoordinateSystemChildren();
+ if (sublist && !(isBlendContainer && !aCSSBackgroundColor) &&
+ AddCanvasBackgroundColor(sublist, aCanvasFrame, aColor,
+ aCSSBackgroundColor)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void PresShell::AddCanvasBackgroundColorItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ nsIFrame* aFrame,
+ const nsRect& aBounds,
+ nscolor aBackstopColor) {
+ if (aBounds.IsEmpty()) {
+ return;
+ }
+ const bool isViewport = aFrame->IsViewportFrame();
+ nscolor canvasColor;
+ if (isViewport) {
+ canvasColor = mCanvasBackground.mViewportColor;
+ } else if (aFrame->IsPageContentFrame()) {
+ canvasColor = mCanvasBackground.mPageColor;
+ } else {
+ // We don't want to add an item for the canvas background color if the frame
+ // (sub)tree we are painting doesn't include any canvas frames.
+ return;
+ }
+ const nscolor bgcolor = NS_ComposeColors(aBackstopColor, canvasColor);
+ if (NS_GET_A(bgcolor) == 0) {
+ return;
+ }
+
+ // To make layers work better, we want to avoid having a big non-scrolled
+ // color background behind a scrolled transparent background. Instead, we'll
+ // try to move the color background into the scrolled content by making
+ // nsDisplayCanvasBackground paint it.
+ bool addedScrollingBackgroundColor = false;
+ if (isViewport) {
+ if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
+ nsCanvasFrame* canvasFrame = do_QueryFrame(sf->GetScrolledFrame());
+ if (canvasFrame && canvasFrame->IsVisibleForPainting()) {
+ // TODO: We should be able to set canvas background color during display
+ // list building to avoid calling this function.
+ addedScrollingBackgroundColor = AddCanvasBackgroundColor(
+ aList, canvasFrame, bgcolor, mCanvasBackground.mCSSSpecified);
+ }
+ }
+ }
+
+ // With async scrolling, we'd like to have two instances of the background
+ // color: one that scrolls with the content (for the reasons stated above),
+ // and one underneath which does not scroll with the content, but which can
+ // be shown during checkerboarding and overscroll and the dynamic toolbar
+ // movement.
+ // We can only do that if the color is opaque.
+ bool forceUnscrolledItem =
+ nsLayoutUtils::UsesAsyncScrolling(aFrame) && NS_GET_A(bgcolor) == 255;
+
+ if (!addedScrollingBackgroundColor || forceUnscrolledItem) {
+ const bool isRootContentDocumentCrossProcess =
+ mPresContext->IsRootContentDocumentCrossProcess();
+ MOZ_ASSERT_IF(
+ !aFrame->GetParent() && isRootContentDocumentCrossProcess &&
+ mPresContext->HasDynamicToolbar(),
+ aBounds.Size() ==
+ nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ mPresContext, aFrame->InkOverflowRectRelativeToSelf().Size()));
+
+ nsDisplaySolidColor* item = MakeDisplayItem<nsDisplaySolidColor>(
+ aBuilder, aFrame, aBounds, bgcolor);
+ if (addedScrollingBackgroundColor && isRootContentDocumentCrossProcess) {
+ item->SetIsCheckerboardBackground();
+ }
+ AddDisplayItemToBottom(aBuilder, aList, item);
+ }
+}
+
+bool PresShell::IsTransparentContainerElement() const {
+ if (mDocument->IsInitialDocument()) {
+ switch (StaticPrefs::layout_css_initial_document_transparency()) {
+ case 3:
+ return true;
+ case 2:
+ if (!mDocument->IsTopLevelContentDocument()) {
+ return true;
+ }
+ [[fallthrough]];
+ case 1:
+ if (mDocument->IsLikelyContentInaccessibleTopLevelAboutBlank()) {
+ return true;
+ }
+ [[fallthrough]];
+ default:
+ break;
+ }
+ }
+
+ nsPresContext* pc = GetPresContext();
+ if (!pc->IsRootContentDocumentCrossProcess()) {
+ if (mDocument->IsInChromeDocShell()) {
+ return true;
+ }
+ // Frames are transparent except if their used embedder color-scheme is
+ // mismatched, in which case we use an opaque background to avoid
+ // black-on-black or white-on-white text, see
+ // https://github.com/w3c/csswg-drafts/issues/4772
+ if (BrowsingContext* bc = mDocument->GetBrowsingContext()) {
+ switch (bc->GetEmbedderColorSchemes().mUsed) {
+ case dom::PrefersColorSchemeOverride::Light:
+ return pc->DefaultBackgroundColorScheme() == ColorScheme::Light;
+ case dom::PrefersColorSchemeOverride::Dark:
+ return pc->DefaultBackgroundColorScheme() == ColorScheme::Dark;
+ case dom::PrefersColorSchemeOverride::None:
+ case dom::PrefersColorSchemeOverride::EndGuard_:
+ break;
+ }
+ }
+ return true;
+ }
+
+ nsIDocShell* docShell = pc->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+ nsPIDOMWindowOuter* pwin = docShell->GetWindow();
+ if (!pwin) {
+ return false;
+ }
+ if (Element* containerElement = pwin->GetFrameElementInternal()) {
+ return containerElement->HasAttr(nsGkAtoms::transparent);
+ }
+ if (BrowserChild* tab = BrowserChild::GetFrom(docShell)) {
+ // Check if presShell is the top PresShell. Only the top can influence the
+ // canvas background color.
+ return this == tab->GetTopLevelPresShell() && tab->IsTransparent();
+ }
+ return false;
+}
+
+nscolor PresShell::GetDefaultBackgroundColorToDraw() const {
+ if (!mPresContext) {
+ return NS_RGB(255, 255, 255);
+ }
+ return mPresContext->DefaultBackgroundColor();
+}
+
+void PresShell::UpdateCanvasBackground() {
+ mCanvasBackground = ComputeCanvasBackground();
+}
+
+struct SingleCanvasBackground {
+ nscolor mColor = 0;
+ bool mCSSSpecified = false;
+};
+
+static SingleCanvasBackground ComputeSingleCanvasBackground(nsIFrame* aCanvas) {
+ MOZ_ASSERT(aCanvas->IsCanvasFrame());
+ const nsIFrame* bgFrame = nsCSSRendering::FindBackgroundFrame(aCanvas);
+ nscolor color = NS_RGBA(0, 0, 0, 0);
+ bool drawBackgroundImage = false;
+ bool drawBackgroundColor = false;
+ if (!bgFrame->IsThemed()) {
+ // Ignore the CSS background-color if -moz-appearance is used.
+ color = nsCSSRendering::DetermineBackgroundColor(
+ aCanvas->PresContext(), bgFrame->Style(), aCanvas, drawBackgroundImage,
+ drawBackgroundColor);
+ }
+ return {color, drawBackgroundColor};
+}
+
+PresShell::CanvasBackground PresShell::ComputeCanvasBackground() const {
+ // If we have a frame tree and it has style information that
+ // specifies the background color of the canvas, update our local
+ // cache of that color.
+ nsIFrame* canvas = GetCanvasFrame();
+ if (!canvas) {
+ nscolor color = GetDefaultBackgroundColorToDraw();
+ // If the root element of the document (ie html) has style 'display: none'
+ // then the document's background color does not get drawn; return the color
+ // we actually draw.
+ return {color, color, false};
+ }
+
+ auto viewportBg = ComputeSingleCanvasBackground(canvas);
+ if (!IsTransparentContainerElement()) {
+ viewportBg.mColor =
+ NS_ComposeColors(GetDefaultBackgroundColorToDraw(), viewportBg.mColor);
+ }
+ nscolor pageColor = viewportBg.mColor;
+ nsCanvasFrame* docElementCb =
+ mFrameConstructor->GetDocElementContainingBlock();
+ if (canvas != docElementCb) {
+ // We're in paged mode / print / print-preview, and just computed the "root"
+ // canvas background. Compute the doc element containing block background
+ // too.
+ MOZ_ASSERT(mPresContext->IsRootPaginatedDocument());
+ pageColor = ComputeSingleCanvasBackground(docElementCb).mColor;
+ }
+ return {viewportBg.mColor, pageColor, viewportBg.mCSSSpecified};
+}
+
+nscolor PresShell::ComputeBackstopColor(nsView* aDisplayRoot) {
+ nsIWidget* widget = aDisplayRoot->GetWidget();
+ if (widget &&
+ (widget->GetTransparencyMode() != widget::TransparencyMode::Opaque ||
+ widget->WidgetPaintsBackground())) {
+ // Within a transparent widget, so the backstop color must be
+ // totally transparent.
+ return NS_RGBA(0, 0, 0, 0);
+ }
+ // Within an opaque widget (or no widget at all), so the backstop
+ // color must be totally opaque. The user's default background
+ // as reported by the prescontext is guaranteed to be opaque.
+ return GetDefaultBackgroundColorToDraw();
+}
+
+struct PaintParams {
+ nscolor mBackgroundColor;
+};
+
+WindowRenderer* PresShell::GetWindowRenderer() {
+ NS_ASSERTION(mViewManager, "Should have view manager");
+
+ nsView* rootView = mViewManager->GetRootView();
+ if (rootView) {
+ if (nsIWidget* widget = rootView->GetWidget()) {
+ return widget->GetWindowRenderer();
+ }
+ }
+ return nullptr;
+}
+
+bool PresShell::AsyncPanZoomEnabled() {
+ NS_ASSERTION(mViewManager, "Should have view manager");
+ nsView* rootView = mViewManager->GetRootView();
+ if (rootView) {
+ if (nsIWidget* widget = rootView->GetWidget()) {
+ return widget->AsyncPanZoomEnabled();
+ }
+ }
+ return gfxPlatform::AsyncPanZoomEnabled();
+}
+
+nsresult PresShell::SetResolutionAndScaleTo(float aResolution,
+ ResolutionChangeOrigin aOrigin) {
+ if (!(aResolution > 0.0)) {
+ return NS_ERROR_ILLEGAL_VALUE;
+ }
+ if (aResolution == mResolution.valueOr(0.0)) {
+ MOZ_ASSERT(mResolution.isSome());
+ return NS_OK;
+ }
+
+ // GetResolution handles mResolution being nothing by returning 1 so this
+ // is checking that the resolution is actually changing.
+ bool resolutionUpdated = (aResolution != GetResolution());
+
+ mLastResolutionChangeOrigin = aOrigin;
+
+ RenderingState state(this);
+ state.mResolution = Some(aResolution);
+ SetRenderingState(state);
+ if (mMobileViewportManager) {
+ mMobileViewportManager->ResolutionUpdated(aOrigin);
+ }
+ // Changing the resolution changes the visual viewport size which may
+ // make the current visual viewport offset out-of-bounds (if the size
+ // increased). APZ will reconcile this by sending a clamped visual
+ // viewport offset on the next repaint, but to avoid main-thread code
+ // observing an out-of-bounds offset until then, reclamp it here.
+ if (IsVisualViewportOffsetSet()) {
+ SetVisualViewportOffset(GetVisualViewportOffset(),
+ GetLayoutViewportOffset());
+ }
+ if (aOrigin == ResolutionChangeOrigin::Apz) {
+ mResolutionUpdatedByApz = true;
+ } else if (resolutionUpdated) {
+ mResolutionUpdated = true;
+ }
+
+ if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+ window->VisualViewport()->PostResizeEvent();
+ }
+
+ return NS_OK;
+}
+
+float PresShell::GetCumulativeResolution() const {
+ float resolution = GetResolution();
+ nsPresContext* parentCtx = GetPresContext()->GetParentPresContext();
+ if (parentCtx) {
+ resolution *= parentCtx->PresShell()->GetCumulativeResolution();
+ }
+ return resolution;
+}
+
+void PresShell::SetRestoreResolution(float aResolution,
+ LayoutDeviceIntSize aDisplaySize) {
+ if (mMobileViewportManager) {
+ mMobileViewportManager->SetRestoreResolution(aResolution, aDisplaySize);
+ }
+}
+
+void PresShell::SetRenderingState(const RenderingState& aState) {
+ if (GetResolution() != aState.mResolution.valueOr(1.f)) {
+ if (nsIFrame* frame = GetRootFrame()) {
+ frame->SchedulePaint();
+ }
+ }
+
+ mRenderingStateFlags = aState.mRenderingStateFlags;
+ mResolution = aState.mResolution;
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfResolutionChange(this, GetResolution());
+ }
+#endif
+}
+
+void PresShell::SynthesizeMouseMove(bool aFromScroll) {
+ if (!StaticPrefs::layout_reflow_synthMouseMove()) return;
+
+ if (mPaintingSuppressed || !mIsActive || !mPresContext) {
+ return;
+ }
+
+ if (!mPresContext->IsRoot()) {
+ if (PresShell* rootPresShell = GetRootPresShell()) {
+ rootPresShell->SynthesizeMouseMove(aFromScroll);
+ }
+ return;
+ }
+
+ if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
+ return;
+
+ if (!mSynthMouseMoveEvent.IsPending()) {
+ RefPtr<nsSynthMouseMoveEvent> ev =
+ new nsSynthMouseMoveEvent(this, aFromScroll);
+
+ GetPresContext()->RefreshDriver()->AddRefreshObserver(
+ ev, FlushType::Display, "Synthetic mouse move event");
+ mSynthMouseMoveEvent = std::move(ev);
+ }
+}
+
+static nsView* FindFloatingViewContaining(nsPresContext* aRootPresContext,
+ nsIWidget* aRootWidget,
+ const LayoutDeviceIntPoint& aPt) {
+ nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForPoint(
+ aRootPresContext, aRootWidget, aPt,
+ nsLayoutUtils::GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets);
+ return popupFrame ? popupFrame->GetView() : nullptr;
+}
+
+/*
+ * This finds the first view with a frame that contains the given point in a
+ * postorder traversal of the view tree, assuming that the point is not in a
+ * floating view. It assumes that only floating views extend outside the bounds
+ * of their parents.
+ *
+ * This methods should only be called if FindFloatingViewContaining returns
+ * null.
+ *
+ * aPt is relative aRelativeToView with the viewport type
+ * aRelativeToViewportType. aRelativeToView will always have a frame. If aView
+ * has a frame then aRelativeToView will be aView. (The reason aRelativeToView
+ * and aView are separate is because we need to traverse into views without
+ * frames (ie the inner view of a subdocument frame) but we can only easily
+ * transform between views using TransformPoint which takes frames.)
+ */
+static nsView* FindViewContaining(nsView* aRelativeToView,
+ ViewportType aRelativeToViewportType,
+ nsView* aView, nsPoint aPt) {
+ MOZ_ASSERT(aRelativeToView->GetFrame());
+
+ if (aView->GetVisibility() == ViewVisibility::Hide) {
+ return nullptr;
+ }
+
+ nsIFrame* frame = aView->GetFrame();
+ if (frame) {
+ if (!frame->PresShell()->IsActive() ||
+ !frame->IsVisibleConsideringAncestors(
+ nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) {
+ return nullptr;
+ }
+
+ // We start out in visual coords and then if we cross the zoom boundary we
+ // become in layout coords. The zoom boundary always occurs in a document
+ // with IsRootContentDocumentCrossProcess. The root view of such a document
+ // is outside the zoom boundary and any child view must be inside the zoom
+ // boundary because we only create views for certain kinds of frames and
+ // none of them can be between the root frame and the zoom boundary.
+ bool crossingZoomBoundary = false;
+ if (aRelativeToViewportType == ViewportType::Visual) {
+ if (!aRelativeToView->GetParent() ||
+ aRelativeToView->GetViewManager() !=
+ aRelativeToView->GetParent()->GetViewManager()) {
+ if (aRelativeToView->GetFrame()
+ ->PresContext()
+ ->IsRootContentDocumentCrossProcess()) {
+ crossingZoomBoundary = true;
+ }
+ }
+ }
+
+ ViewportType nextRelativeToViewportType = aRelativeToViewportType;
+ if (crossingZoomBoundary) {
+ nextRelativeToViewportType = ViewportType::Layout;
+ }
+
+ nsLayoutUtils::TransformResult result = nsLayoutUtils::TransformPoint(
+ RelativeTo{aRelativeToView->GetFrame(), aRelativeToViewportType},
+ RelativeTo{frame, nextRelativeToViewportType}, aPt);
+ if (result != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return nullptr;
+ }
+
+ // Even though aPt is in visual coordinates until we cross the zoom boundary
+ // it is valid to compare it to view coords (which are in layout coords)
+ // because visual coords are the same as layout coords for every view
+ // outside of the zoom boundary except for the root view of the root content
+ // document.
+ // For the root view of the root content document, its bounds don't
+ // actually correspond to what is visible when we have a
+ // MobileViewportManager. So we skip the hit test. This is okay because the
+ // point has already been hit test: 1) if we are the root view in the
+ // process then the point comes from a real mouse event so it must have been
+ // over our widget, or 2) if we are the root of a subdocument then
+ // hittesting against the view of the subdocument frame that contains us
+ // already happened and succeeded before getting here.
+ if (!crossingZoomBoundary) {
+ if (!aView->GetDimensions().Contains(aPt)) {
+ return nullptr;
+ }
+ }
+
+ aRelativeToView = aView;
+ aRelativeToViewportType = nextRelativeToViewportType;
+ }
+
+ for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
+ nsView* r =
+ FindViewContaining(aRelativeToView, aRelativeToViewportType, v, aPt);
+ if (r) return r;
+ }
+
+ return frame ? aView : nullptr;
+}
+
+static BrowserBridgeChild* GetChildBrowser(nsView* aView) {
+ if (!aView) {
+ return nullptr;
+ }
+ nsIFrame* frame = aView->GetFrame();
+ if (!frame && aView->GetParent()) {
+ // If frame is null then view is an anonymous inner view, and we want
+ // the frame from the corresponding outer view.
+ frame = aView->GetParent()->GetFrame();
+ }
+ if (!frame || !frame->GetContent()) {
+ return nullptr;
+ }
+ return BrowserBridgeChild::GetFrom(frame->GetContent());
+}
+
+void PresShell::ProcessSynthMouseMoveEvent(bool aFromScroll) {
+ // If drag session has started, we shouldn't synthesize mousemove event.
+ nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
+ if (dragSession) {
+ mSynthMouseMoveEvent.Forget();
+ return;
+ }
+
+ // allow new event to be posted while handling this one only if the
+ // source of the event is a scroll (to prevent infinite reflow loops)
+ if (aFromScroll) {
+ mSynthMouseMoveEvent.Forget();
+ }
+
+ nsView* rootView = mViewManager ? mViewManager->GetRootView() : nullptr;
+ if (mMouseLocation == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) ||
+ !rootView || !rootView->HasWidget() || !mPresContext) {
+ mSynthMouseMoveEvent.Forget();
+ return;
+ }
+
+ NS_ASSERTION(mPresContext->IsRoot(), "Only a root pres shell should be here");
+
+ // Hold a ref to ourselves so DispatchEvent won't destroy us (since
+ // we need to access members after we call DispatchEvent).
+ RefPtr<PresShell> kungFuDeathGrip(this);
+
+#ifdef DEBUG_MOUSE_LOCATION
+ printf("[ps=%p]synthesizing mouse move to (%d,%d)\n", this, mMouseLocation.x,
+ mMouseLocation.y);
+#endif
+
+ int32_t APD = mPresContext->AppUnitsPerDevPixel();
+
+ // We need a widget to put in the event we are going to dispatch so we look
+ // for a view that has a widget and the mouse location is over. We first look
+ // for floating views, if there isn't one we use the root view. |view| holds
+ // that view.
+ nsView* view = nullptr;
+
+ // The appunits per devpixel ratio of |view|.
+ int32_t viewAPD;
+
+ // mRefPoint will be mMouseLocation relative to the widget of |view|, the
+ // widget we will put in the event we dispatch, in viewAPD appunits
+ nsPoint refpoint(0, 0);
+
+ // We always dispatch the event to the pres shell that contains the view that
+ // the mouse is over. pointVM is the VM of that pres shell.
+ nsViewManager* pointVM = nullptr;
+
+ if (rootView->GetFrame()) {
+ view = FindFloatingViewContaining(
+ mPresContext, rootView->GetWidget(),
+ LayoutDeviceIntPoint::FromAppUnitsToNearest(
+ mMouseLocation + rootView->ViewToWidgetOffset(), APD));
+ }
+
+ nsView* pointView = view;
+ if (!view) {
+ view = rootView;
+ if (rootView->GetFrame()) {
+ pointView = FindViewContaining(rootView, ViewportType::Visual, rootView,
+ mMouseLocation);
+ } else {
+ pointView = rootView;
+ }
+ // pointView can be null in situations related to mouse capture
+ pointVM = (pointView ? pointView : view)->GetViewManager();
+ refpoint = mMouseLocation + rootView->ViewToWidgetOffset();
+ viewAPD = APD;
+ } else {
+ pointVM = view->GetViewManager();
+ nsIFrame* frame = view->GetFrame();
+ NS_ASSERTION(frame, "floating views can't be anonymous");
+ viewAPD = frame->PresContext()->AppUnitsPerDevPixel();
+ refpoint = mMouseLocation;
+ DebugOnly<nsLayoutUtils::TransformResult> result =
+ nsLayoutUtils::TransformPoint(
+ RelativeTo{rootView->GetFrame(), ViewportType::Visual},
+ RelativeTo{frame, ViewportType::Layout}, refpoint);
+ MOZ_ASSERT(result == nsLayoutUtils::TRANSFORM_SUCCEEDED);
+ refpoint += view->ViewToWidgetOffset();
+ }
+ NS_ASSERTION(view->GetWidget(), "view should have a widget here");
+ WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
+ WidgetMouseEvent::eSynthesized);
+
+ // If the last cursor location was set by a synthesized mouse event for tests,
+ // running test should expect a restyle or a DOM mutation under the cursor may
+ // cause mouse boundary events in a remote process if the cursor is over a
+ // remote content. Therefore, the events should not be ignored by
+ // PresShell::HandleEvent in the remote process. So we need to mark the
+ // synthesized eMouseMove as "synthesized for tests".
+ event.mFlags.mIsSynthesizedForTests =
+ mMouseLocationWasSetBySynthesizedMouseEventForTests;
+
+ event.mRefPoint =
+ LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
+ event.mButtons = PresShell::sMouseButtons;
+ // XXX set event.mModifiers ?
+ // XXX mnakano I think that we should get the latest information from widget.
+
+ if (BrowserBridgeChild* bbc = GetChildBrowser(pointView)) {
+ // If we have a BrowserBridgeChild, we're going to be dispatching this
+ // mouse event into an OOP iframe of the current document.
+ event.mLayersId = bbc->GetLayersId();
+ bbc->SendDispatchSynthesizedMouseEvent(event);
+ } else if (RefPtr<PresShell> presShell = pointVM->GetPresShell()) {
+ // Since this gets run in a refresh tick there isn't an InputAPZContext on
+ // the stack from the nsBaseWidget. We need to simulate one with at least
+ // the correct target guid, so that the correct callback transform gets
+ // applied if this event goes to a child process. The input block id is set
+ // to 0 because this is a synthetic event which doesn't really belong to any
+ // input block. Same for the APZ response field.
+ InputAPZContext apzContext(mMouseEventTargetGuid, 0, nsEventStatus_eIgnore);
+ presShell->DispatchSynthMouseMove(&event);
+ }
+
+ if (!aFromScroll) {
+ mSynthMouseMoveEvent.Forget();
+ }
+}
+
+/* static */
+void PresShell::MarkFramesInListApproximatelyVisible(
+ const nsDisplayList& aList) {
+ for (nsDisplayItem* item : aList) {
+ nsDisplayList* sublist = item->GetChildren();
+ if (sublist) {
+ MarkFramesInListApproximatelyVisible(*sublist);
+ continue;
+ }
+
+ nsIFrame* frame = item->Frame();
+ MOZ_ASSERT(frame);
+
+ if (!frame->TrackingVisibility()) {
+ continue;
+ }
+
+ // Use the presshell containing the frame.
+ PresShell* presShell = frame->PresShell();
+ MOZ_ASSERT(!presShell->AssumeAllFramesVisible());
+ if (presShell->mApproximatelyVisibleFrames.EnsureInserted(frame)) {
+ // The frame was added to mApproximatelyVisibleFrames, so increment its
+ // visible count.
+ frame->IncApproximateVisibleCount();
+ }
+ }
+}
+
+/* static */
+void PresShell::DecApproximateVisibleCount(
+ VisibleFrames& aFrames, const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */) {
+ for (nsIFrame* frame : aFrames) {
+ // Decrement the frame's visible count if we're still tracking its
+ // visibility. (We may not be, if the frame disabled visibility tracking
+ // after we added it to the visible frames list.)
+ if (frame->TrackingVisibility()) {
+ frame->DecApproximateVisibleCount(aNonvisibleAction);
+ }
+ }
+}
+
+void PresShell::RebuildApproximateFrameVisibilityDisplayList(
+ const nsDisplayList& aList) {
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
+ mApproximateFrameVisibilityVisited = true;
+
+ // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
+ // them in oldApproxVisibleFrames.
+ VisibleFrames oldApproximatelyVisibleFrames =
+ std::move(mApproximatelyVisibleFrames);
+
+ MarkFramesInListApproximatelyVisible(aList);
+
+ DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
+}
+
+/* static */
+void PresShell::ClearApproximateFrameVisibilityVisited(nsView* aView,
+ bool aClear) {
+ nsViewManager* vm = aView->GetViewManager();
+ if (aClear) {
+ PresShell* presShell = vm->GetPresShell();
+ if (!presShell->mApproximateFrameVisibilityVisited) {
+ presShell->ClearApproximatelyVisibleFramesList();
+ }
+ presShell->mApproximateFrameVisibilityVisited = false;
+ }
+ for (nsView* v = aView->GetFirstChild(); v; v = v->GetNextSibling()) {
+ ClearApproximateFrameVisibilityVisited(v, v->GetViewManager() != vm);
+ }
+}
+
+void PresShell::ClearApproximatelyVisibleFramesList(
+ const Maybe<OnNonvisible>& aNonvisibleAction
+ /* = Nothing() */) {
+ DecApproximateVisibleCount(mApproximatelyVisibleFrames, aNonvisibleAction);
+ mApproximatelyVisibleFrames.Clear();
+}
+
+void PresShell::MarkFramesInSubtreeApproximatelyVisible(
+ nsIFrame* aFrame, const nsRect& aRect, bool aRemoveOnly /* = false */) {
+ MOZ_DIAGNOSTIC_ASSERT(aFrame, "aFrame arg should be a valid frame pointer");
+ MOZ_ASSERT(aFrame->PresShell() == this, "wrong presshell");
+
+ if (aFrame->TrackingVisibility() && aFrame->StyleVisibility()->IsVisible() &&
+ (!aRemoveOnly ||
+ aFrame->GetVisibility() == Visibility::ApproximatelyVisible)) {
+ MOZ_ASSERT(!AssumeAllFramesVisible());
+ if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
+ // The frame was added to mApproximatelyVisibleFrames, so increment its
+ // visible count.
+ aFrame->IncApproximateVisibleCount();
+ }
+ }
+
+ nsSubDocumentFrame* subdocFrame = do_QueryFrame(aFrame);
+ if (subdocFrame) {
+ PresShell* presShell = subdocFrame->GetSubdocumentPresShellForPainting(
+ nsSubDocumentFrame::IGNORE_PAINT_SUPPRESSION);
+ if (presShell && !presShell->AssumeAllFramesVisible()) {
+ nsRect rect = aRect;
+ nsIFrame* root = presShell->GetRootFrame();
+ if (root) {
+ rect.MoveBy(aFrame->GetOffsetToCrossDoc(root));
+ } else {
+ rect.MoveBy(-aFrame->GetContentRectRelativeToSelf().TopLeft());
+ }
+ rect = rect.ScaleToOtherAppUnitsRoundOut(
+ aFrame->PresContext()->AppUnitsPerDevPixel(),
+ presShell->GetPresContext()->AppUnitsPerDevPixel());
+
+ presShell->RebuildApproximateFrameVisibility(&rect);
+ }
+ return;
+ }
+
+ nsRect rect = aRect;
+
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(aFrame);
+ if (scrollFrame) {
+ bool ignoreDisplayPort = false;
+ if (DisplayPortUtils::IsMissingDisplayPortBaseRect(aFrame->GetContent())) {
+ // We can properly set the base rect for root scroll frames on top level
+ // and root content documents. Otherwise the base rect we compute might
+ // be way too big without the limiting that
+ // nsHTMLScrollFrame::DecideScrollableLayer does, so we just ignore the
+ // displayport in that case.
+ nsPresContext* pc = aFrame->PresContext();
+ if (scrollFrame->IsRootScrollFrameOfDocument() &&
+ (pc->IsRootContentDocumentCrossProcess() ||
+ (pc->IsChrome() && !pc->GetParentPresContext()))) {
+ nsRect baseRect(
+ nsPoint(), nsLayoutUtils::CalculateCompositionSizeForFrame(aFrame));
+ DisplayPortUtils::SetDisplayPortBase(aFrame->GetContent(), baseRect);
+ } else {
+ ignoreDisplayPort = true;
+ }
+ }
+
+ nsRect displayPort;
+ bool usingDisplayport =
+ !ignoreDisplayPort &&
+ DisplayPortUtils::GetDisplayPortForVisibilityTesting(
+ aFrame->GetContent(), &displayPort);
+
+ scrollFrame->NotifyApproximateFrameVisibilityUpdate(!usingDisplayport);
+
+ if (usingDisplayport) {
+ rect = displayPort;
+ } else {
+ rect = rect.Intersect(scrollFrame->GetScrollPortRect());
+ }
+ rect = scrollFrame->ExpandRectToNearlyVisible(rect);
+ }
+
+ bool preserves3DChildren = aFrame->Extend3DContext();
+
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (listID == FrameChildListID::Popup) {
+ // We assume all frames in popups are visible, so we skip them here.
+ continue;
+ }
+
+ for (nsIFrame* child : list) {
+ // Note: This assert should be trivially satisfied, just by virtue of how
+ // nsFrameList and its iterator works (with nullptr being an end-of-list
+ // sentinel which should terminate the loop). But we do somehow get
+ // crash reports inside this loop that suggest `child` is null...
+ MOZ_DIAGNOSTIC_ASSERT(child, "shouldn't have null values in child lists");
+ nsRect r = rect - child->GetPosition();
+ if (!r.IntersectRect(r, child->InkOverflowRect())) {
+ continue;
+ }
+ if (child->IsTransformed()) {
+ // for children of a preserve3d element we just pass down the same dirty
+ // rect
+ if (!preserves3DChildren ||
+ !child->Combines3DTransformWithAncestors()) {
+ const nsRect overflow = child->InkOverflowRectRelativeToSelf();
+ nsRect out;
+ if (nsDisplayTransform::UntransformRect(r, overflow, child, &out)) {
+ r = out;
+ } else {
+ r.SetEmpty();
+ }
+ }
+ }
+ MarkFramesInSubtreeApproximatelyVisible(child, r, aRemoveOnly);
+ }
+ }
+}
+
+void PresShell::RebuildApproximateFrameVisibility(
+ nsRect* aRect, bool aRemoveOnly /* = false */) {
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "already visited?");
+ mApproximateFrameVisibilityVisited = true;
+
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ // Remove the entries of the mApproximatelyVisibleFrames hashtable and put
+ // them in oldApproximatelyVisibleFrames.
+ VisibleFrames oldApproximatelyVisibleFrames =
+ std::move(mApproximatelyVisibleFrames);
+
+ nsRect vis(nsPoint(0, 0), rootFrame->GetSize());
+ if (aRect) {
+ vis = *aRect;
+ }
+
+ // If we are in-process root but not the top level content, we need to take
+ // the intersection with the iframe visible rect.
+ if (mPresContext->IsRootContentDocumentInProcess() &&
+ !mPresContext->IsRootContentDocumentCrossProcess()) {
+ // There are two possibilities that we can't get the iframe's visible
+ // rect other than the iframe is out side of ancestors' display ports.
+ // a) the BrowserChild is being torn down
+ // b) the visible rect hasn't been delivered the BrowserChild
+ // In both cases we consider the visible rect is empty.
+ Maybe<nsRect> visibleRect;
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
+ visibleRect = browserChild->GetVisibleRect();
+ }
+ vis = vis.Intersect(visibleRect.valueOr(nsRect()));
+ }
+
+ MarkFramesInSubtreeApproximatelyVisible(rootFrame, vis, aRemoveOnly);
+
+ DecApproximateVisibleCount(oldApproximatelyVisibleFrames);
+}
+
+void PresShell::UpdateApproximateFrameVisibility() {
+ DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ false);
+}
+
+void PresShell::DoUpdateApproximateFrameVisibility(bool aRemoveOnly) {
+ MOZ_ASSERT(
+ !mPresContext || mPresContext->IsRootContentDocumentInProcess(),
+ "Updating approximate frame visibility on a non-root content document?");
+
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ if (mHaveShutDown || mIsDestroying) {
+ return;
+ }
+
+ // call update on that frame
+ nsIFrame* rootFrame = GetRootFrame();
+ if (!rootFrame) {
+ ClearApproximatelyVisibleFramesList(Some(OnNonvisible::DiscardImages));
+ return;
+ }
+
+ RebuildApproximateFrameVisibility(/* aRect = */ nullptr, aRemoveOnly);
+ ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+
+#ifdef DEBUG_FRAME_VISIBILITY_DISPLAY_LIST
+ // This can be used to debug the frame walker by comparing beforeFrameList
+ // and mApproximatelyVisibleFrames in RebuildFrameVisibilityDisplayList to see
+ // if they produce the same results (mApproximatelyVisibleFrames holds the
+ // frames the display list thinks are visible, beforeFrameList holds the
+ // frames the frame walker thinks are visible).
+ nsDisplayListBuilder builder(
+ rootFrame, nsDisplayListBuilderMode::FRAME_VISIBILITY, false);
+ nsRect updateRect(nsPoint(0, 0), rootFrame->GetSize());
+ nsIFrame* rootScroll = GetRootScrollFrame();
+ if (rootScroll) {
+ nsIContent* content = rootScroll->GetContent();
+ if (content) {
+ Unused << nsLayoutUtils::GetDisplayPortForVisibilityTesting(
+ content, &updateRect, RelativeTo::ScrollFrame);
+ }
+
+ if (IgnoringViewportScrolling()) {
+ builder.SetIgnoreScrollFrame(rootScroll);
+ }
+ }
+ builder.IgnorePaintSuppression();
+ builder.EnterPresShell(rootFrame);
+ nsDisplayList list;
+ rootFrame->BuildDisplayListForStackingContext(&builder, updateRect, &list);
+ builder.LeavePresShell(rootFrame, &list);
+
+ RebuildApproximateFrameVisibilityDisplayList(list);
+
+ ClearApproximateFrameVisibilityVisited(rootFrame->GetView(), true);
+
+ list.DeleteAll(&builder);
+#endif
+}
+
+bool PresShell::AssumeAllFramesVisible() {
+ if (!StaticPrefs::layout_framevisibility_enabled() || !mPresContext ||
+ !mDocument) {
+ return true;
+ }
+
+ // We assume all frames are visible in print, print preview, chrome, and
+ // resource docs and don't keep track of them.
+ if (mPresContext->Type() == nsPresContext::eContext_PrintPreview ||
+ mPresContext->Type() == nsPresContext::eContext_Print ||
+ mPresContext->IsChrome() || mDocument->IsResourceDoc()) {
+ return true;
+ }
+
+ // If we're assuming all frames are visible in the top level content
+ // document, we need to in subdocuments as well. Otherwise we can get in a
+ // situation where things like animations won't work in subdocuments because
+ // their frames appear not to be visible, since we won't schedule an image
+ // visibility update if the top level content document is assuming all
+ // frames are visible.
+ //
+ // Note that it's not safe to call IsRootContentDocumentInProcess() if we're
+ // currently being destroyed, so we have to check that first.
+ if (!mHaveShutDown && !mIsDestroying &&
+ !mPresContext->IsRootContentDocumentInProcess()) {
+ nsPresContext* presContext =
+ mPresContext->GetInProcessRootContentDocumentPresContext();
+ if (presContext && presContext->PresShell()->AssumeAllFramesVisible()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void PresShell::ScheduleApproximateFrameVisibilityUpdateSoon() {
+ if (AssumeAllFramesVisible()) {
+ return;
+ }
+
+ if (!mPresContext) {
+ return;
+ }
+
+ nsRefreshDriver* refreshDriver = mPresContext->RefreshDriver();
+ if (!refreshDriver) {
+ return;
+ }
+
+ // Ask the refresh driver to update frame visibility soon.
+ refreshDriver->ScheduleFrameVisibilityUpdate();
+}
+
+void PresShell::ScheduleApproximateFrameVisibilityUpdateNow() {
+ if (AssumeAllFramesVisible()) {
+ return;
+ }
+
+ if (!mPresContext->IsRootContentDocumentInProcess()) {
+ nsPresContext* presContext =
+ mPresContext->GetInProcessRootContentDocumentPresContext();
+ if (!presContext) return;
+ MOZ_ASSERT(presContext->IsRootContentDocumentInProcess(),
+ "Didn't get a root prescontext from "
+ "GetInProcessRootContentDocumentPresContext?");
+ presContext->PresShell()->ScheduleApproximateFrameVisibilityUpdateNow();
+ return;
+ }
+
+ if (mHaveShutDown || mIsDestroying) {
+ return;
+ }
+
+ if (mUpdateApproximateFrameVisibilityEvent.IsPending()) {
+ return;
+ }
+
+ RefPtr<nsRunnableMethod<PresShell>> event =
+ NewRunnableMethod("PresShell::UpdateApproximateFrameVisibility", this,
+ &PresShell::UpdateApproximateFrameVisibility);
+ nsresult rv = mDocument->Dispatch(do_AddRef(event));
+
+ if (NS_SUCCEEDED(rv)) {
+ mUpdateApproximateFrameVisibilityEvent = std::move(event);
+ }
+}
+
+void PresShell::EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame) {
+ if (!aFrame->TrackingVisibility()) {
+ return;
+ }
+
+ if (AssumeAllFramesVisible()) {
+ aFrame->IncApproximateVisibleCount();
+ return;
+ }
+
+#ifdef DEBUG
+ // Make sure it's in this pres shell.
+ nsCOMPtr<nsIContent> content = aFrame->GetContent();
+ if (content) {
+ PresShell* presShell = content->OwnerDoc()->GetPresShell();
+ MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
+ }
+#endif
+
+ if (mApproximatelyVisibleFrames.EnsureInserted(aFrame)) {
+ // We inserted a new entry.
+ aFrame->IncApproximateVisibleCount();
+ }
+}
+
+void PresShell::RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame) {
+#ifdef DEBUG
+ // Make sure it's in this pres shell.
+ nsCOMPtr<nsIContent> content = aFrame->GetContent();
+ if (content) {
+ PresShell* presShell = content->OwnerDoc()->GetPresShell();
+ MOZ_ASSERT(!presShell || presShell == this, "wrong shell");
+ }
+#endif
+
+ if (AssumeAllFramesVisible()) {
+ MOZ_ASSERT(mApproximatelyVisibleFrames.Count() == 0,
+ "Shouldn't have any frames in the table");
+ return;
+ }
+
+ if (mApproximatelyVisibleFrames.EnsureRemoved(aFrame) &&
+ aFrame->TrackingVisibility()) {
+ // aFrame was in the hashtable, and we're still tracking its visibility,
+ // so we need to decrement its visible count.
+ aFrame->DecApproximateVisibleCount();
+ }
+}
+
+void PresShell::PaintAndRequestComposite(nsView* aView, PaintFlags aFlags) {
+ if (!mIsActive) {
+ return;
+ }
+
+ WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
+ NS_ASSERTION(renderer, "Must be in paint event");
+ if (renderer->AsFallback()) {
+ // The fallback renderer doesn't do any retaining, so we
+ // just need to notify the view and widget that we're invalid, and
+ // we'll do a paint+composite from the PaintWindow callback.
+ GetViewManager()->InvalidateView(aView);
+ return;
+ }
+
+ // Otherwise we're a retained WebRenderLayerManager, so we want to call
+ // Paint to update with any changes and push those to WR.
+ PaintInternalFlags flags = PaintInternalFlags::None;
+ if (aFlags & PaintFlags::PaintSyncDecodeImages) {
+ flags |= PaintInternalFlags::PaintSyncDecodeImages;
+ }
+ PaintInternal(aView, flags);
+}
+
+void PresShell::SyncPaintFallback(nsView* aView) {
+ if (!mIsActive) {
+ return;
+ }
+
+ WindowRenderer* renderer = aView->GetWidget()->GetWindowRenderer();
+ NS_ASSERTION(renderer->AsFallback(),
+ "Can't do Sync paint for remote renderers");
+ if (!renderer->AsFallback()) {
+ return;
+ }
+
+ PaintInternal(aView, PaintInternalFlags::PaintComposite);
+ GetPresContext()->NotifyDidPaintForSubtree();
+}
+
+void PresShell::PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags) {
+ nsCString url;
+ nsIURI* uri = mDocument->GetDocumentURI();
+ Document* contentRoot = GetPrimaryContentDocument();
+ if (contentRoot) {
+ uri = contentRoot->GetDocumentURI();
+ }
+ url = uri ? uri->GetSpecOrDefault() : "N/A"_ns;
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
+ "Paint", GRAPHICS, Substring(url, std::min(size_t(128), url.Length())));
+
+ Maybe<js::AutoAssertNoContentJS> nojs;
+
+ // On Android, Flash can call into content JS during painting, so we can't
+ // assert there. However, we don't rely on this assertion on Android because
+ // we don't paint while JS is running.
+#if !defined(MOZ_WIDGET_ANDROID)
+ if (!(aFlags & PaintInternalFlags::PaintComposite)) {
+ // We need to allow content JS when the flag is set since we may trigger
+ // MozAfterPaint events in content in those cases.
+ nojs.emplace(dom::danger::GetJSContext());
+ }
+#endif
+
+ NS_ASSERTION(!mIsDestroying, "painting a destroyed PresShell");
+ NS_ASSERTION(aViewToPaint, "null view");
+
+ MOZ_ASSERT(!mApproximateFrameVisibilityVisited, "Should have been cleared");
+
+ if (!mIsActive) {
+ return;
+ }
+
+ if (StaticPrefs::apz_keyboard_enabled_AtStartup()) {
+ // Update the focus target for async keyboard scrolling. This will be
+ // forwarded to APZ by nsDisplayList::PaintRoot. We need to to do this
+ // before we enter the paint phase because dispatching eVoid events can
+ // cause layout to happen.
+ mAPZFocusTarget = FocusTarget(this, mAPZFocusSequenceNumber);
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(presContext, Paint);
+
+ nsIFrame* frame = aViewToPaint->GetFrame();
+
+ WindowRenderer* renderer = aViewToPaint->GetWidget()->GetWindowRenderer();
+ NS_ASSERTION(renderer, "Must be in paint event");
+ WebRenderLayerManager* layerManager = renderer->AsWebRender();
+
+ // Whether or not we should set first paint when painting is suppressed
+ // is debatable. For now we'll do it because B2G relied on first paint
+ // to configure the viewport and we only want to do that when we have
+ // real content to paint. See Bug 798245
+ if (mIsFirstPaint && !mPaintingSuppressed) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("PresShell::Paint, first paint, this=%p", this));
+
+ if (layerManager) {
+ layerManager->SetIsFirstPaint();
+ }
+ mIsFirstPaint = false;
+ }
+
+ if (!renderer->BeginTransaction(url)) {
+ return;
+ }
+
+ // Send an updated focus target with this transaction. Be sure to do this
+ // before we paint in the case this is an empty transaction.
+ if (layerManager) {
+ layerManager->SetFocusTarget(mAPZFocusTarget);
+ }
+
+ if (frame) {
+ if (!(aFlags & PaintInternalFlags::PaintSyncDecodeImages) &&
+ !frame->HasAnyStateBits(NS_FRAME_UPDATE_LAYER_TREE)) {
+ if (layerManager) {
+ layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
+ }
+
+ if (renderer->EndEmptyTransaction(
+ (aFlags & PaintInternalFlags::PaintComposite)
+ ? WindowRenderer::END_DEFAULT
+ : WindowRenderer::END_NO_COMPOSITE)) {
+ return;
+ }
+ }
+ frame->RemoveStateBits(NS_FRAME_UPDATE_LAYER_TREE);
+ }
+
+ nscolor bgcolor = ComputeBackstopColor(aViewToPaint);
+ PaintFrameFlags flags =
+ PaintFrameFlags::WidgetLayers | PaintFrameFlags::ExistingTransaction;
+
+ // We force sync-decode for printing / print-preview (printing already does
+ // this from nsPageSequenceFrame::PrintNextSheet).
+ // We also force sync-decoding via pref for reftests.
+ if (aFlags & PaintInternalFlags::PaintSyncDecodeImages ||
+ mDocument->IsStaticDocument() ||
+ StaticPrefs::image_decode_sync_enabled()) {
+ flags |= PaintFrameFlags::SyncDecodeImages;
+ }
+ if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ flags |= PaintFrameFlags::ForWebRender;
+ }
+
+ if (frame) {
+ // We can paint directly into the widget using its layer manager.
+ nsLayoutUtils::PaintFrame(nullptr, frame, nsRegion(), bgcolor,
+ nsDisplayListBuilderMode::Painting, flags);
+ return;
+ }
+
+ bgcolor = NS_ComposeColors(bgcolor, mCanvasBackground.mViewportColor);
+
+ if (renderer->GetBackendType() == layers::LayersBackend::LAYERS_WR) {
+ LayoutDeviceRect bounds = LayoutDeviceRect::FromAppUnits(
+ presContext->GetVisibleArea(), presContext->AppUnitsPerDevPixel());
+ WebRenderBackgroundData data(wr::ToLayoutRect(bounds),
+ wr::ToColorF(ToDeviceColor(bgcolor)));
+ WrFiltersHolder wrFilters;
+
+ layerManager->SetTransactionIdAllocator(presContext->RefreshDriver());
+ layerManager->EndTransactionWithoutLayer(nullptr, nullptr,
+ std::move(wrFilters), &data, 0);
+ return;
+ }
+
+ FallbackRenderer* fallback = renderer->AsFallback();
+ MOZ_ASSERT(fallback);
+
+ if (aFlags & PaintInternalFlags::PaintComposite) {
+ nsIntRect bounds = presContext->GetVisibleArea().ToOutsidePixels(
+ presContext->AppUnitsPerDevPixel());
+ fallback->EndTransactionWithColor(bounds, ToDeviceColor(bgcolor));
+ }
+}
+
+// static
+void PresShell::SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
+ WidgetEvent* aEvent) {
+ // If capture was set for pointer lock, don't unlock unless we are coming
+ // out of pointer lock explicitly.
+ if (!aContent && sCapturingContentInfo.mPointerLock &&
+ !(aFlags & CaptureFlags::PointerLock)) {
+ return;
+ }
+
+ sCapturingContentInfo.mContent = nullptr;
+ sCapturingContentInfo.mRemoteTarget = nullptr;
+
+ // only set capturing content if allowed or the
+ // CaptureFlags::IgnoreAllowedState or CaptureFlags::PointerLock are used.
+ if ((aFlags & CaptureFlags::IgnoreAllowedState) ||
+ sCapturingContentInfo.mAllowed || (aFlags & CaptureFlags::PointerLock)) {
+ if (aContent) {
+ sCapturingContentInfo.mContent = aContent;
+ }
+ if (aEvent) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(aEvent->mMessage == eMouseDown);
+ MOZ_ASSERT(aEvent->HasBeenPostedToRemoteProcess());
+ sCapturingContentInfo.mRemoteTarget =
+ BrowserParent::GetLastMouseRemoteTarget();
+ MOZ_ASSERT(sCapturingContentInfo.mRemoteTarget);
+ }
+ // CaptureFlags::PointerLock is the same as
+ // CaptureFlags::RetargetToElement & CaptureFlags::IgnoreAllowedState.
+ sCapturingContentInfo.mRetargetToElement =
+ !!(aFlags & CaptureFlags::RetargetToElement) ||
+ !!(aFlags & CaptureFlags::PointerLock);
+ sCapturingContentInfo.mPreventDrag =
+ !!(aFlags & CaptureFlags::PreventDragStart);
+ sCapturingContentInfo.mPointerLock = !!(aFlags & CaptureFlags::PointerLock);
+ }
+}
+
+nsIContent* PresShell::GetCurrentEventContent() {
+ if (mCurrentEventContent &&
+ mCurrentEventContent->GetComposedDoc() != mDocument) {
+ mCurrentEventContent = nullptr;
+ mCurrentEventFrame = nullptr;
+ }
+ return mCurrentEventContent;
+}
+
+nsIFrame* PresShell::GetCurrentEventFrame() {
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return nullptr;
+ }
+
+ // GetCurrentEventContent() makes sure the content is still in the
+ // same document that this pres shell belongs to. If not, then the
+ // frame shouldn't get an event, nor should we even assume its safe
+ // to try and find the frame.
+ nsIContent* content = GetCurrentEventContent();
+ if (!mCurrentEventFrame && content) {
+ mCurrentEventFrame = content->GetPrimaryFrame();
+ MOZ_ASSERT(!mCurrentEventFrame ||
+ mCurrentEventFrame->PresContext()->GetPresShell() == this);
+ }
+ return mCurrentEventFrame;
+}
+
+already_AddRefed<nsIContent> PresShell::GetEventTargetContent(
+ WidgetEvent* aEvent) {
+ nsCOMPtr<nsIContent> content = GetCurrentEventContent();
+ if (!content) {
+ nsIFrame* currentEventFrame = GetCurrentEventFrame();
+ if (currentEventFrame) {
+ currentEventFrame->GetContentForEvent(aEvent, getter_AddRefs(content));
+ NS_ASSERTION(!content || content->GetComposedDoc() == mDocument,
+ "handing out content from a different doc");
+ }
+ }
+ return content.forget();
+}
+
+void PresShell::PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent) {
+ if (mCurrentEventFrame || mCurrentEventContent) {
+ mCurrentEventFrameStack.InsertElementAt(0, mCurrentEventFrame);
+ mCurrentEventContentStack.InsertObjectAt(mCurrentEventContent, 0);
+ }
+ mCurrentEventFrame = aFrame;
+ mCurrentEventContent = aContent;
+}
+
+void PresShell::PopCurrentEventInfo() {
+ mCurrentEventFrame = nullptr;
+ mCurrentEventContent = nullptr;
+
+ if (0 != mCurrentEventFrameStack.Length()) {
+ mCurrentEventFrame = mCurrentEventFrameStack.ElementAt(0);
+ mCurrentEventFrameStack.RemoveElementAt(0);
+ mCurrentEventContent = mCurrentEventContentStack.ObjectAt(0);
+ mCurrentEventContentStack.RemoveObjectAt(0);
+
+ // Don't use it if it has moved to a different document.
+ if (mCurrentEventContent &&
+ mCurrentEventContent->GetComposedDoc() != mDocument) {
+ mCurrentEventContent = nullptr;
+ mCurrentEventFrame = nullptr;
+ }
+ }
+}
+
+// static
+bool PresShell::EventHandler::InZombieDocument(nsIContent* aContent) {
+ // If a content node points to a null document, or the document is not
+ // attached to a window, then it is possibly in a zombie document,
+ // about to be replaced by a newly loading document.
+ // Such documents cannot handle DOM events.
+ // It might actually be in a node not attached to any document,
+ // in which case there is not parent presshell to retarget it to.
+ Document* doc = aContent->GetComposedDoc();
+ return !doc || !doc->GetWindow();
+}
+
+already_AddRefed<nsPIDOMWindowOuter> PresShell::GetRootWindow() {
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+ if (window) {
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = window->GetPrivateRoot();
+ NS_ASSERTION(rootWindow, "nsPIDOMWindow::GetPrivateRoot() returns NULL");
+ return rootWindow.forget();
+ }
+
+ // If we don't have DOM window, we're zombie, we should find the root window
+ // with our parent shell.
+ RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
+ NS_ENSURE_TRUE(parentPresShell, nullptr);
+ return parentPresShell->GetRootWindow();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+PresShell::GetFocusedDOMWindowInOurWindow() {
+ nsCOMPtr<nsPIDOMWindowOuter> rootWindow = GetRootWindow();
+ NS_ENSURE_TRUE(rootWindow, nullptr);
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ nsFocusManager::GetFocusedDescendant(rootWindow,
+ nsFocusManager::eIncludeAllDescendants,
+ getter_AddRefs(focusedWindow));
+ return focusedWindow.forget();
+}
+
+already_AddRefed<nsIContent> PresShell::GetFocusedContentInOurWindow() const {
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm && mDocument) {
+ RefPtr<Element> focusedElement;
+ fm->GetFocusedElementForWindow(mDocument->GetWindow(), false, nullptr,
+ getter_AddRefs(focusedElement));
+ return focusedElement.forget();
+ }
+ return nullptr;
+}
+
+already_AddRefed<PresShell> PresShell::GetParentPresShellForEventHandling() {
+ if (!mPresContext) {
+ return nullptr;
+ }
+
+ // Now, find the parent pres shell and send the event there
+ RefPtr<nsDocShell> docShell = mPresContext->GetDocShell();
+ if (!docShell) {
+ docShell = mForwardingContainer.get();
+ }
+
+ // Might have gone away, or never been around to start with
+ if (!docShell) {
+ return nullptr;
+ }
+
+ BrowsingContext* bc = docShell->GetBrowsingContext();
+ if (!bc) {
+ return nullptr;
+ }
+
+ RefPtr<BrowsingContext> parentBC;
+ if (XRE_IsParentProcess()) {
+ parentBC = bc->Canonical()->GetParentCrossChromeBoundary();
+ } else {
+ parentBC = bc->GetParent();
+ }
+
+ RefPtr<nsIDocShell> parentDocShell =
+ parentBC ? parentBC->GetDocShell() : nullptr;
+ if (!parentDocShell) {
+ return nullptr;
+ }
+
+ RefPtr<PresShell> parentPresShell = parentDocShell->GetPresShell();
+ return parentPresShell.forget();
+}
+
+nsresult PresShell::EventHandler::RetargetEventToParent(
+ WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
+ // Send this events straight up to the parent pres shell.
+ // We do this for keystroke events in zombie documents or if either a frame
+ // or a root content is not present.
+ // That way at least the UI key bindings can work.
+
+ RefPtr<PresShell> parentPresShell = GetParentPresShellForEventHandling();
+ NS_ENSURE_TRUE(parentPresShell, NS_ERROR_FAILURE);
+
+ // Fake the event as though it's from the parent pres shell's root frame.
+ return parentPresShell->HandleEvent(parentPresShell->GetRootFrame(),
+ aGUIEvent, true, aEventStatus);
+}
+
+void PresShell::DisableNonTestMouseEvents(bool aDisable) {
+ sDisableNonTestMouseEvents = aDisable;
+}
+
+bool PresShell::MouseLocationWasSetBySynthesizedMouseEventForTests() const {
+ if (!mPresContext) {
+ return false;
+ }
+ if (mPresContext->IsRoot()) {
+ return mMouseLocationWasSetBySynthesizedMouseEventForTests;
+ }
+ PresShell* rootPresShell = GetRootPresShell();
+ return rootPresShell &&
+ rootPresShell->mMouseLocationWasSetBySynthesizedMouseEventForTests;
+}
+
+nsPoint PresShell::GetEventLocation(const WidgetMouseEvent& aEvent) const {
+ nsIFrame* rootFrame = GetRootFrame();
+ if (rootFrame) {
+ RelativeTo relativeTo{rootFrame};
+ if (rootFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ relativeTo.mViewportType = ViewportType::Visual;
+ }
+ return nsLayoutUtils::GetEventCoordinatesRelativeTo(&aEvent, relativeTo);
+ }
+
+ nsView* rootView = mViewManager->GetRootView();
+ return nsLayoutUtils::TranslateWidgetToView(mPresContext, aEvent.mWidget,
+ aEvent.mRefPoint, rootView);
+}
+
+void PresShell::RecordPointerLocation(WidgetGUIEvent* aEvent) {
+ if (!mPresContext) {
+ return;
+ }
+
+ if (!mPresContext->IsRoot()) {
+ PresShell* rootPresShell = GetRootPresShell();
+ if (rootPresShell) {
+ rootPresShell->RecordPointerLocation(aEvent);
+ }
+ return;
+ }
+
+ if ((aEvent->mMessage == eMouseMove &&
+ aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
+ aEvent->mMessage == eMouseEnterIntoWidget ||
+ aEvent->mMessage == eMouseDown || aEvent->mMessage == eMouseUp) {
+ mMouseLocation = GetEventLocation(*aEvent->AsMouseEvent());
+ mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
+ mMouseLocationWasSetBySynthesizedMouseEventForTests =
+ aEvent->mFlags.mIsSynthesizedForTests;
+#ifdef DEBUG_MOUSE_LOCATION
+ if (aEvent->mMessage == eMouseEnterIntoWidget) {
+ printf("[ps=%p]got mouse enter for %p\n", this, aEvent->mWidget);
+ }
+ printf("[ps=%p]setting mouse location to (%d,%d)\n", this, mMouseLocation.x,
+ mMouseLocation.y);
+#endif
+ if (aEvent->mMessage == eMouseEnterIntoWidget) {
+ SynthesizeMouseMove(false);
+ }
+ } else if (aEvent->mMessage == eMouseExitFromWidget) {
+ // Although we only care about the mouse moving into an area for which this
+ // pres shell doesn't receive mouse move events, we don't check which widget
+ // the mouse exit was for since this seems to vary by platform. Hopefully
+ // this won't matter at all since we'll get the mouse move or enter after
+ // the mouse exit when the mouse moves from one of our widgets into another.
+ mMouseLocation = nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ mMouseEventTargetGuid = InputAPZContext::GetTargetLayerGuid();
+ mMouseLocationWasSetBySynthesizedMouseEventForTests =
+ aEvent->mFlags.mIsSynthesizedForTests;
+#ifdef DEBUG_MOUSE_LOCATION
+ printf("[ps=%p]got mouse exit for %p\n", this, aEvent->mWidget);
+ printf("[ps=%p]clearing mouse location\n", this);
+#endif
+ } else if ((aEvent->mMessage == ePointerMove &&
+ aEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eReal) ||
+ aEvent->mMessage == ePointerDown ||
+ aEvent->mMessage == ePointerUp) {
+ // TODO: instead, encapsulate `mMouseLocation` and
+ // `mLastOverWindowPointerLocation` in a struct.
+ mLastOverWindowPointerLocation = GetEventLocation(*aEvent->AsMouseEvent());
+ }
+}
+
+void PresShell::nsSynthMouseMoveEvent::Revoke() {
+ if (mPresShell) {
+ mPresShell->GetPresContext()->RefreshDriver()->RemoveRefreshObserver(
+ this, FlushType::Display);
+ mPresShell = nullptr;
+ }
+}
+
+// static
+nsIFrame* PresShell::EventHandler::GetNearestFrameContainingPresShell(
+ PresShell* aPresShell) {
+ nsViewManager* vm = aPresShell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+ nsView* view = vm->GetRootView();
+ while (view && !view->GetFrame()) {
+ view = view->GetParent();
+ }
+
+ nsIFrame* frame = nullptr;
+ if (view) {
+ frame = view->GetFrame();
+ }
+
+ return frame;
+}
+
+static CallState FlushThrottledStyles(Document& aDocument) {
+ PresShell* presShell = aDocument.GetPresShell();
+ if (presShell && presShell->IsVisible()) {
+ if (nsPresContext* presContext = presShell->GetPresContext()) {
+ presContext->RestyleManager()->UpdateOnlyAnimationStyles();
+ }
+ }
+
+ aDocument.EnumerateSubDocuments(FlushThrottledStyles);
+ return CallState::Continue;
+}
+
+bool PresShell::CanDispatchEvent(const WidgetGUIEvent* aEvent) const {
+ bool rv =
+ mPresContext && !mHaveShutDown && nsContentUtils::IsSafeToRunScript();
+ if (aEvent) {
+ rv &= (aEvent && aEvent->mWidget && !aEvent->mWidget->Destroyed());
+ }
+ return rv;
+}
+
+/* static */
+PresShell* PresShell::GetShellForEventTarget(nsIFrame* aFrame,
+ nsIContent* aContent) {
+ if (aFrame) {
+ return aFrame->PresShell();
+ }
+ if (aContent) {
+ Document* doc = aContent->GetComposedDoc();
+ if (!doc) {
+ return nullptr;
+ }
+ return doc->GetPresShell();
+ }
+ return nullptr;
+}
+
+/* static */
+PresShell* PresShell::GetShellForTouchEvent(WidgetGUIEvent* aEvent) {
+ switch (aEvent->mMessage) {
+ case eTouchMove:
+ case eTouchCancel:
+ case eTouchEnd: {
+ // get the correct shell to dispatch to
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ for (dom::Touch* touch : touchEvent->mTouches) {
+ if (!touch) {
+ return nullptr;
+ }
+
+ RefPtr<dom::Touch> oldTouch =
+ TouchManager::GetCapturedTouch(touch->Identifier());
+ if (!oldTouch) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(oldTouch->GetTarget());
+ if (!content) {
+ return nullptr;
+ }
+
+ nsIFrame* contentFrame = content->GetPrimaryFrame();
+ if (!contentFrame) {
+ return nullptr;
+ }
+
+ PresShell* presShell = contentFrame->PresContext()->GetPresShell();
+ if (presShell) {
+ return presShell;
+ }
+ }
+ return nullptr;
+ }
+ default:
+ return nullptr;
+ }
+}
+
+nsresult PresShell::HandleEvent(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ // Running tests must not expect that some mouse boundary events are fired
+ // when something occurs in the parent process, e.g., when a popup is
+ // opened/closed at the last mouse cursor position in the parent process (the
+ // position may be different from the position which stored in this process).
+ // Therefore, let's ignore synthesized mouse events coming form another
+ // process if and only if they are not caused by the API.
+ if (aGUIEvent->CameFromAnotherProcess() && XRE_IsContentProcess() &&
+ !aGUIEvent->mFlags.mIsSynthesizedForTests &&
+ MouseLocationWasSetBySynthesizedMouseEventForTests()) {
+ switch (aGUIEvent->mMessage) {
+ // Synthesized eMouseMove will case mouse boundary events like mouseover,
+ // mouseout, and :hover state is changed at dispatching the events.
+ case eMouseMove:
+ // eMouseExitFromWidget comes from the parent process if the cursor
+ // crosses a puppet widget boundary. Then, the event will be handled as a
+ // synthesized eMouseMove in this process and may cause unexpected
+ // `mouseout` and `mouseleave`.
+ case eMouseExitFromWidget:
+ // eMouseEnterIntoWidget causes updating the hover state under the event
+ // position which may be different from the last cursor position
+ // synthesized in this process.
+ case eMouseEnterIntoWidget:
+ if (!aGUIEvent->AsMouseEvent()->IsReal()) {
+ return NS_OK;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Here we are granting some delays to ensure that user input events are
+ // created while the page content may not be visible to the user are not
+ // processed.
+ // The main purpose of this is to avoid user inputs are handled in the
+ // new document where as the user inputs were originally targeting some
+ // content in the old document.
+ if (!CanHandleUserInputEvents(aGUIEvent)) {
+ return NS_OK;
+ }
+
+ if (mPresContext) {
+ switch (aGUIEvent->mMessage) {
+ case eMouseMove:
+ if (!aGUIEvent->AsMouseEvent()->IsReal()) {
+ break;
+ }
+ [[fallthrough]];
+ case eMouseDown:
+ case eMouseUp: {
+ // We should flush pending mousemove event now because some mouse
+ // boundary events which should've already been dispatched before a user
+ // input may have not been dispatched. E.g., if a mousedown event
+ // listener removed or appended an element under the cursor and mouseup
+ // event comes immediately after that, mouseover or mouseout may have
+ // not been dispatched on the new element yet.
+ // XXX If eMouseMove is not propery dispatched before eMouseDown and
+ // a `mousedown` event listener removes the event target or its
+ // ancestor, eMouseOver will be dispatched between eMouseDown and
+ // eMouseUp. That could cause unexpected behavior if a `mouseover`
+ // event listener assumes it's always disptached before `mousedown`.
+ // However, we're not sure whether it could happen with users' input.
+ // FIXME: Perhaps, we need to do this for all events which are directly
+ // caused by user input, e.g., eKeyDown, etc.
+ RefPtr<PresShell> rootPresShell =
+ mPresContext->IsRoot() ? this : GetRootPresShell();
+ if (rootPresShell && rootPresShell->mSynthMouseMoveEvent.IsPending()) {
+ AutoWeakFrame frameForPresShellWeak(aFrameForPresShell);
+ RefPtr<nsSynthMouseMoveEvent> synthMouseMoveEvent =
+ rootPresShell->mSynthMouseMoveEvent.get();
+ synthMouseMoveEvent->Run();
+ if (IsDestroying()) {
+ return NS_OK;
+ }
+ // XXX If the frame or "this" is reframed, it might be better to
+ // recompute the frame. However, it could treat the user input on
+ // unexpected element. Therefore, we should not do that until we'd
+ // get a bug report caused by that.
+ if (MOZ_UNLIKELY(!frameForPresShellWeak.IsAlive())) {
+ return NS_OK;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ EventHandler eventHandler(*this);
+ return eventHandler.HandleEvent(aFrameForPresShell, aGUIEvent,
+ aDontRetargetEvents, aEventStatus);
+}
+
+nsresult PresShell::EventHandler::HandleEvent(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_DIAGNOSTIC_ASSERT(aGUIEvent->IsTrusted());
+ MOZ_ASSERT(aEventStatus);
+
+ NS_ASSERTION(aFrameForPresShell, "aFrameForPresShell should be not null");
+
+ // Update the latest focus sequence number with this new sequence number;
+ // the next transasction that gets sent to the compositor will carry this over
+ if (mPresShell->mAPZFocusSequenceNumber < aGUIEvent->mFocusSequenceNumber) {
+ mPresShell->mAPZFocusSequenceNumber = aGUIEvent->mFocusSequenceNumber;
+ }
+
+ if (mPresShell->IsDestroying() ||
+ (PresShell::sDisableNonTestMouseEvents &&
+ !aGUIEvent->mFlags.mIsSynthesizedForTests &&
+ aGUIEvent->HasMouseEventMessage())) {
+ return NS_OK;
+ }
+
+ mPresShell->RecordPointerLocation(aGUIEvent);
+
+ if (MaybeHandleEventWithAccessibleCaret(aFrameForPresShell, aGUIEvent,
+ aEventStatus)) {
+ // Handled by AccessibleCaretEventHub.
+ return NS_OK;
+ }
+
+ if (MaybeDiscardEvent(aGUIEvent)) {
+ // Cannot handle the event for now.
+ return NS_OK;
+ }
+
+ if (!aDontRetargetEvents) {
+ // If aGUIEvent should be handled in another PresShell, we should call its
+ // HandleEvent() and do nothing here.
+ nsresult rv = NS_OK;
+ if (MaybeHandleEventWithAnotherPresShell(aFrameForPresShell, aGUIEvent,
+ aEventStatus, &rv)) {
+ // Handled by another PresShell or nobody can handle the event.
+ return rv;
+ }
+ }
+
+ if (MaybeDiscardOrDelayKeyboardEvent(aGUIEvent)) {
+ // The event is discarded or put into the delayed event queue.
+ return NS_OK;
+ }
+
+ if (aGUIEvent->IsUsingCoordinates()) {
+ return HandleEventUsingCoordinates(aFrameForPresShell, aGUIEvent,
+ aEventStatus, aDontRetargetEvents);
+ }
+
+ // Activation events need to be dispatched even if no frame was found, since
+ // we don't want the focus to be out of sync.
+ if (!aFrameForPresShell) {
+ if (!NS_EVENT_NEEDS_FRAME(aGUIEvent)) {
+ // Push nullptr for both current event target content and frame since
+ // there is no frame but the event does not require a frame.
+ AutoCurrentEventInfoSetter eventInfoSetter(*this);
+ return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+ nullptr);
+ }
+
+ if (aGUIEvent->HasKeyEventMessage()) {
+ // Keypress events in new blank tabs should not be completely thrown away.
+ // Retarget them -- the parent chrome shell might make use of them.
+ return RetargetEventToParent(aGUIEvent, aEventStatus);
+ }
+
+ return NS_OK;
+ }
+
+ if (aGUIEvent->IsTargetedAtFocusedContent()) {
+ return HandleEventAtFocusedContent(aGUIEvent, aEventStatus);
+ }
+
+ return HandleEventWithFrameForPresShell(aFrameForPresShell, aGUIEvent,
+ aEventStatus);
+}
+
+nsresult PresShell::EventHandler::HandleEventUsingCoordinates(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus, bool aDontRetargetEvents) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
+ MOZ_ASSERT(aEventStatus);
+
+ // Flush pending notifications to handle the event with the latest layout.
+ // But if it causes destroying the frame for mPresShell, stop handling the
+ // event. (why?)
+ AutoWeakFrame weakFrame(aFrameForPresShell);
+ MaybeFlushPendingNotifications(aGUIEvent);
+ if (!weakFrame.IsAlive()) {
+ *aEventStatus = nsEventStatus_eIgnore;
+ return NS_OK;
+ }
+
+ // XXX Retrieving capturing content here. However, some of the following
+ // methods allow to run script. So, isn't it possible the capturing
+ // content outdated?
+ nsCOMPtr<nsIContent> capturingContent =
+ EventHandler::GetCapturingContentFor(aGUIEvent);
+
+ if (GetDocument() && aGUIEvent->mClass == eTouchEventClass) {
+ PointerLockManager::Unlock();
+ }
+
+ nsIFrame* frameForPresShell = MaybeFlushThrottledStyles(aFrameForPresShell);
+ if (NS_WARN_IF(!frameForPresShell)) {
+ return NS_OK;
+ }
+
+ bool isCapturingContentIgnored = false;
+ bool isCaptureRetargeted = false;
+ nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEvent(
+ frameForPresShell, aGUIEvent, capturingContent,
+ &isCapturingContentIgnored, &isCaptureRetargeted);
+ if (isCapturingContentIgnored) {
+ capturingContent = nullptr;
+ }
+
+ // The order to generate pointer event is
+ // 1. check pending pointer capture.
+ // 2. check if there is a capturing content.
+ // 3. hit test
+ // 4. dispatch pointer events
+ // 5. check whether the targets of all Touch instances are in the same
+ // document and suppress invalid instances.
+ // 6. dispatch mouse or touch events.
+
+ // Try to keep frame for following check, because frame can be damaged
+ // during MaybeProcessPointerCapture.
+ {
+ AutoWeakFrame frameKeeper(rootFrameToHandleEvent);
+ PointerEventHandler::MaybeProcessPointerCapture(aGUIEvent);
+ // Prevent application crashes, in case damaged frame.
+ if (!frameKeeper.IsAlive()) {
+ NS_WARNING("Nothing to handle this event!");
+ return NS_OK;
+ }
+ }
+
+ // Only capture mouse events and pointer events.
+ RefPtr<Element> pointerCapturingElement =
+ PointerEventHandler::GetPointerCapturingElement(aGUIEvent);
+
+ if (pointerCapturingElement) {
+ rootFrameToHandleEvent = pointerCapturingElement->GetPrimaryFrame();
+ if (!rootFrameToHandleEvent) {
+ return HandleEventWithPointerCapturingContentWithoutItsFrame(
+ aFrameForPresShell, aGUIEvent, pointerCapturingElement, aEventStatus);
+ }
+ }
+
+ WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
+ bool isWindowLevelMouseExit =
+ (aGUIEvent->mMessage == eMouseExitFromWidget) &&
+ (mouseEvent &&
+ (mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePlatformTopLevel ||
+ mouseEvent->mExitFrom.value() == WidgetMouseEvent::ePuppet));
+
+ // Get the frame at the event point. However, don't do this if we're
+ // capturing and retargeting the event because the captured frame will
+ // be used instead below. Also keep using the root frame if we're dealing
+ // with a window-level mouse exit event since we want to start sending
+ // mouse out events at the root EventStateManager.
+ EventTargetData eventTargetData(rootFrameToHandleEvent);
+ if (!isCaptureRetargeted && !isWindowLevelMouseExit &&
+ !pointerCapturingElement) {
+ if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
+ rootFrameToHandleEvent, aGUIEvent, &eventTargetData)) {
+ *aEventStatus = nsEventStatus_eIgnore;
+ return NS_OK;
+ }
+ }
+
+ // if a node is capturing the mouse, check if the event needs to be
+ // retargeted at the capturing content instead. This will be the case when
+ // capture retargeting is being used, no frame was found or the frame's
+ // content is not a descendant of the capturing content.
+ if (capturingContent && !pointerCapturingElement &&
+ (PresShell::sCapturingContentInfo.mRetargetToElement ||
+ !eventTargetData.GetFrameContent() ||
+ !nsContentUtils::ContentIsCrossDocDescendantOf(
+ eventTargetData.GetFrameContent(), capturingContent))) {
+ // A check was already done above to ensure that capturingContent is
+ // in this presshell.
+ NS_ASSERTION(capturingContent->OwnerDoc() == GetDocument(),
+ "Unexpected document");
+ nsIFrame* capturingFrame = capturingContent->GetPrimaryFrame();
+ if (capturingFrame) {
+ eventTargetData.SetFrameAndComputePresShell(capturingFrame);
+ }
+ }
+
+ if (NS_WARN_IF(!eventTargetData.GetFrame())) {
+ return NS_OK;
+ }
+
+ // Suppress mouse event if it's being targeted at an element inside
+ // a document which needs events suppressed
+ if (MaybeDiscardOrDelayMouseEvent(eventTargetData.GetFrame(), aGUIEvent)) {
+ return NS_OK;
+ }
+
+ // Check if we have an active EventStateManager which isn't the
+ // EventStateManager of the current PresContext. If that is the case, and
+ // mouse is over some ancestor document, forward event handling to the
+ // active document. This way content can get mouse events even when mouse
+ // is over the chrome or outside the window.
+ if (eventTargetData.MaybeRetargetToActiveDocument(aGUIEvent) &&
+ NS_WARN_IF(!eventTargetData.GetFrame())) {
+ return NS_OK;
+ }
+
+ // Wheel events only apply to elements. If this is a wheel event, attempt to
+ // update the event target from the current wheel transaction before we
+ // compute the element from the target frame.
+ eventTargetData.UpdateWheelEventTarget(aGUIEvent);
+
+ if (!eventTargetData.ComputeElementFromFrame(aGUIEvent)) {
+ return NS_OK;
+ }
+ // Note that even if ComputeElementFromFrame() returns true,
+ // eventTargetData.mContent can be nullptr here.
+
+ // Dispatch a pointer event if Pointer Events is enabled. Note that if
+ // pointer event listeners change the layout, eventTargetData is
+ // automatically updated.
+ if (!DispatchPrecedingPointerEvent(
+ aFrameForPresShell, aGUIEvent, pointerCapturingElement,
+ aDontRetargetEvents, &eventTargetData, aEventStatus)) {
+ return NS_OK;
+ }
+
+ // frame could be null after dispatching pointer events.
+ // XXX Despite of this comment, we update the event target data outside
+ // DispatchPrecedingPointerEvent(). Can we make it call
+ // UpdateTouchEventTarget()?
+ eventTargetData.UpdateTouchEventTarget(aGUIEvent);
+
+ // Handle the event in the correct shell.
+ // We pass the subshell's root frame as the frame to start from. This is
+ // the only correct alternative; if the event was captured then it
+ // must have been captured by us or some ancestor shell and we
+ // now ask the subshell to dispatch it normally.
+ EventHandler eventHandler(*eventTargetData.mPresShell);
+ AutoCurrentEventInfoSetter eventInfoSetter(eventHandler, eventTargetData);
+ // eventTargetData is on the stack and is guaranteed to keep its
+ // mOverrideClickTarget alive, so we can just use MOZ_KnownLive here.
+ nsresult rv = eventHandler.HandleEventWithCurrentEventInfo(
+ aGUIEvent, aEventStatus, true,
+ MOZ_KnownLive(eventTargetData.mOverrideClickTarget));
+ return rv;
+}
+
+bool PresShell::EventHandler::MaybeFlushPendingNotifications(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+
+ switch (aGUIEvent->mMessage) {
+ case eMouseDown:
+ case eMouseUp: {
+ RefPtr<nsPresContext> presContext = mPresShell->GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return false;
+ }
+ uint64_t framesConstructedCount = presContext->FramesConstructedCount();
+ uint64_t framesReflowedCount = presContext->FramesReflowedCount();
+
+ MOZ_KnownLive(mPresShell)->FlushPendingNotifications(FlushType::Layout);
+ return framesConstructedCount != presContext->FramesConstructedCount() ||
+ framesReflowedCount != presContext->FramesReflowedCount();
+ }
+ default:
+ return false;
+ }
+}
+
+// The type of coordinates to use for hit-testing input events
+// that are relative to the RCD's viewport frame.
+// On most platforms, use visual coordinates so that scrollbars
+// can be targeted.
+// On mobile, use layout coordinates because hit-testing in
+// visual coordinates clashes with mobile viewport sizing, where
+// the ViewportFrame is sized to the initial containing block
+// (ICB) size, which is in layout coordinates. This is fine
+// because we don't need to be able to target scrollbars on mobile
+// (scrollbar dragging isn't supported).
+static ViewportType ViewportTypeForInputEventsRelativeToRoot() {
+#ifdef MOZ_WIDGET_ANDROID
+ return ViewportType::Layout;
+#else
+ return ViewportType::Visual;
+#endif
+}
+
+nsIFrame* PresShell::EventHandler::GetFrameToHandleNonTouchEvent(
+ nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aGUIEvent->mClass != eTouchEventClass);
+
+ ViewportType viewportType = ViewportType::Layout;
+ if (aRootFrameToHandleEvent->Type() == LayoutFrameType::Viewport) {
+ nsPresContext* pc = aRootFrameToHandleEvent->PresContext();
+ if (pc->IsChrome()) {
+ viewportType = ViewportType::Visual;
+ } else if (pc->IsRootContentDocumentCrossProcess()) {
+ viewportType = ViewportTypeForInputEventsRelativeToRoot();
+ }
+ }
+ RelativeTo relativeTo{aRootFrameToHandleEvent, viewportType};
+ nsPoint eventPoint =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
+
+ uint32_t flags = 0;
+ if (aGUIEvent->mClass == eMouseEventClass) {
+ WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
+ if (mouseEvent && mouseEvent->mIgnoreRootScrollFrame) {
+ flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
+ }
+ }
+
+ nsIFrame* targetFrame =
+ FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
+ if (!targetFrame) {
+ return aRootFrameToHandleEvent;
+ }
+
+ if (targetFrame->PresShell() == mPresShell) {
+ // If found target is in mPresShell, we've already found it in the latest
+ // layout so that we can use it.
+ return targetFrame;
+ }
+
+ // If target is in a child document, we've not flushed its layout yet.
+ PresShell* childPresShell = targetFrame->PresShell();
+ EventHandler childEventHandler(*childPresShell);
+ AutoWeakFrame weakFrame(aRootFrameToHandleEvent);
+ bool layoutChanged =
+ childEventHandler.MaybeFlushPendingNotifications(aGUIEvent);
+ if (!weakFrame.IsAlive()) {
+ // Stop handling the event if the root frame to handle event is destroyed
+ // by the reflow. (but why?)
+ return nullptr;
+ }
+ if (!layoutChanged) {
+ // If the layout in the child PresShell hasn't been changed, we don't
+ // need to recompute the target.
+ return targetFrame;
+ }
+
+ // Finally, we need to recompute the target with the latest layout.
+ targetFrame =
+ FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
+
+ return targetFrame ? targetFrame : aRootFrameToHandleEvent;
+}
+
+bool PresShell::EventHandler::ComputeEventTargetFrameAndPresShellAtEventPoint(
+ nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
+ EventTargetData* aEventTargetData) {
+ MOZ_ASSERT(aRootFrameToHandleEvent);
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aEventTargetData);
+
+ if (aGUIEvent->mClass == eTouchEventClass) {
+ nsIFrame* targetFrameAtTouchEvent = TouchManager::SetupTarget(
+ aGUIEvent->AsTouchEvent(), aRootFrameToHandleEvent);
+ aEventTargetData->SetFrameAndComputePresShell(targetFrameAtTouchEvent);
+ return true;
+ }
+
+ nsIFrame* targetFrame =
+ GetFrameToHandleNonTouchEvent(aRootFrameToHandleEvent, aGUIEvent);
+ aEventTargetData->SetFrameAndComputePresShell(targetFrame);
+ return !!aEventTargetData->GetFrame();
+}
+
+bool PresShell::EventHandler::DispatchPrecedingPointerEvent(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aPointerCapturingContent, bool aDontRetargetEvents,
+ EventTargetData* aEventTargetData, nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aFrameForPresShell);
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aEventTargetData);
+ MOZ_ASSERT(aEventStatus);
+
+ // Dispatch pointer events from the mouse or touch events. Regarding
+ // pointer events from mouse, we should dispatch those pointer events to
+ // the same target as the source mouse events. We pass the frame found
+ // in hit test to PointerEventHandler and dispatch pointer events to it.
+ //
+ // Regarding pointer events from touch, the behavior is different. Touch
+ // events are dispatched to the same target as the target of touchstart.
+ // Multiple touch points must be dispatched to the same document. Pointer
+ // events from touch can be dispatched to different documents. We Pass the
+ // original frame to PointerEventHandler, reentry PresShell::HandleEvent,
+ // and do hit test for each point.
+ nsIFrame* targetFrame = aGUIEvent->mClass == eTouchEventClass
+ ? aFrameForPresShell
+ : aEventTargetData->GetFrame();
+
+ if (aPointerCapturingContent) {
+ aEventTargetData->mOverrideClickTarget =
+ GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
+ aEventTargetData->mPresShell =
+ PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
+ if (!aEventTargetData->mPresShell) {
+ // If we can't process event for the capturing content, release
+ // the capture.
+ PointerEventHandler::ReleaseIfCaptureByDescendant(
+ aPointerCapturingContent);
+ return false;
+ }
+
+ targetFrame = aPointerCapturingContent->GetPrimaryFrame();
+ aEventTargetData->SetFrameAndContent(targetFrame, aPointerCapturingContent);
+ }
+
+ AutoWeakFrame weakTargetFrame(targetFrame);
+ AutoWeakFrame weakFrame(aEventTargetData->GetFrame());
+ nsCOMPtr<nsIContent> content(aEventTargetData->GetContent());
+ RefPtr<PresShell> presShell(aEventTargetData->mPresShell);
+ nsCOMPtr<nsIContent> targetContent;
+ PointerEventHandler::DispatchPointerFromMouseOrTouch(
+ presShell, aEventTargetData->GetFrame(), content, aGUIEvent,
+ aDontRetargetEvents, aEventStatus, getter_AddRefs(targetContent));
+
+ // If the target frame is alive, the caller should keep handling the event
+ // unless event target frame is destroyed.
+ if (weakTargetFrame.IsAlive()) {
+ return weakFrame.IsAlive();
+ }
+
+ // If the event is not a mouse event, the caller should keep handling the
+ // event unless event target frame is destroyed. Note that this case is
+ // not defined by the spec.
+ if (aGUIEvent->mClass != eMouseEventClass) {
+ return weakFrame.IsAlive();
+ }
+
+ // Spec defines that mouse events must be dispatched to the same target as
+ // the pointer event. If the target is no longer participating in its
+ // ownerDocument's tree, fire the event at the original target's nearest
+ // ancestor node
+ if (!targetContent) {
+ return false;
+ }
+
+ aEventTargetData->SetFrameAndContent(targetContent->GetPrimaryFrame(),
+ targetContent);
+ aEventTargetData->mPresShell = PresShell::GetShellForEventTarget(
+ aEventTargetData->GetFrame(), aEventTargetData->GetContent());
+
+ // If new target PresShel is not found, we cannot keep handling the event.
+ return !!aEventTargetData->mPresShell;
+}
+
+/**
+ * Event retargetting may retarget a mouse event and change the reference point.
+ * If event retargetting changes the reference point of a event that accessible
+ * caret will not handle, restore the original reference point.
+ */
+class AutoEventTargetPointResetter {
+ public:
+ explicit AutoEventTargetPointResetter(WidgetGUIEvent* aGUIEvent)
+ : mGUIEvent(aGUIEvent),
+ mRefPoint(aGUIEvent->mRefPoint),
+ mHandledByAccessibleCaret(false) {}
+
+ void SetHandledByAccessibleCaret() { mHandledByAccessibleCaret = true; }
+
+ ~AutoEventTargetPointResetter() {
+ if (!mHandledByAccessibleCaret) {
+ mGUIEvent->mRefPoint = mRefPoint;
+ }
+ }
+
+ private:
+ WidgetGUIEvent* mGUIEvent;
+ LayoutDeviceIntPoint mRefPoint;
+ bool mHandledByAccessibleCaret;
+};
+
+bool PresShell::EventHandler::MaybeHandleEventWithAccessibleCaret(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aEventStatus);
+
+ // Don't dispatch event to AccessibleCaretEventHub when the event status
+ // is nsEventStatus_eConsumeNoDefault. This might be happened when content
+ // preventDefault on the pointer events. In such case, we also call
+ // preventDefault on mouse events to stop default behaviors.
+ if (*aEventStatus == nsEventStatus_eConsumeNoDefault) {
+ return false;
+ }
+
+ if (!AccessibleCaretEnabled(GetDocument()->GetDocShell())) {
+ return false;
+ }
+
+ // AccessibleCaretEventHub handles only mouse, touch, and keyboard events.
+ if (aGUIEvent->mClass != eMouseEventClass &&
+ aGUIEvent->mClass != eTouchEventClass &&
+ aGUIEvent->mClass != eKeyboardEventClass) {
+ return false;
+ }
+
+ AutoEventTargetPointResetter autoEventTargetPointResetter(aGUIEvent);
+ // First, try the event hub at the event point to handle a long press to
+ // select a word in an unfocused window.
+ do {
+ EventTargetData eventTargetData(nullptr);
+ if (!ComputeEventTargetFrameAndPresShellAtEventPoint(
+ aFrameForPresShell, aGUIEvent, &eventTargetData)) {
+ break;
+ }
+
+ if (!eventTargetData.mPresShell) {
+ break;
+ }
+
+ RefPtr<AccessibleCaretEventHub> eventHub =
+ eventTargetData.mPresShell->GetAccessibleCaretEventHub();
+ if (!eventHub) {
+ break;
+ }
+
+ *aEventStatus = eventHub->HandleEvent(aGUIEvent);
+ if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
+ break;
+ }
+
+ // If the event is consumed, cancel APZC panning by setting
+ // mMultipleActionsPrevented.
+ aGUIEvent->mFlags.mMultipleActionsPrevented = true;
+ autoEventTargetPointResetter.SetHandledByAccessibleCaret();
+ return true;
+ } while (false);
+
+ // Then, we target the event to the event hub at the focused window.
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
+ if (!window) {
+ return false;
+ }
+ RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
+ if (!retargetEventDoc) {
+ return false;
+ }
+ RefPtr<PresShell> presShell = retargetEventDoc->GetPresShell();
+ if (!presShell) {
+ return false;
+ }
+
+ RefPtr<AccessibleCaretEventHub> eventHub =
+ presShell->GetAccessibleCaretEventHub();
+ if (!eventHub) {
+ return false;
+ }
+ *aEventStatus = eventHub->HandleEvent(aGUIEvent);
+ if (*aEventStatus != nsEventStatus_eConsumeNoDefault) {
+ return false;
+ }
+ // If the event is consumed, cancel APZC panning by setting
+ // mMultipleActionsPrevented.
+ aGUIEvent->mFlags.mMultipleActionsPrevented = true;
+ autoEventTargetPointResetter.SetHandledByAccessibleCaret();
+ return true;
+}
+
+bool PresShell::EventHandler::MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+
+ // If it is safe to dispatch events now, don't discard the event.
+ if (nsContentUtils::IsSafeToRunScript()) {
+ return false;
+ }
+
+ // If the event does not cause dispatching DOM event (i.e., internal event),
+ // we can keep handling it even when it's not safe to run script.
+ if (!aGUIEvent->IsAllowedToDispatchDOMEvent()) {
+ return false;
+ }
+
+ // If the event is a composition event, we need to let IMEStateManager know
+ // it's discarded because it needs to listen all composition events to manage
+ // TextComposition instance.
+ if (aGUIEvent->mClass == eCompositionEventClass) {
+ IMEStateManager::OnCompositionEventDiscarded(
+ aGUIEvent->AsCompositionEvent());
+ }
+
+#ifdef DEBUG
+ if (aGUIEvent->IsIMERelatedEvent()) {
+ nsPrintfCString warning("%s event is discarded",
+ ToChar(aGUIEvent->mMessage));
+ NS_WARNING(warning.get());
+ }
+#endif // #ifdef DEBUG
+
+ nsContentUtils::WarnScriptWasIgnored(GetDocument());
+ return true;
+}
+
+// static
+nsIContent* PresShell::EventHandler::GetCapturingContentFor(
+ WidgetGUIEvent* aGUIEvent) {
+ return (aGUIEvent->mClass == ePointerEventClass ||
+ aGUIEvent->mClass == eWheelEventClass ||
+ aGUIEvent->HasMouseEventMessage())
+ ? PresShell::GetCapturingContent()
+ : nullptr;
+}
+
+bool PresShell::EventHandler::GetRetargetEventDocument(
+ WidgetGUIEvent* aGUIEvent, Document** aRetargetEventDocument) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aRetargetEventDocument);
+
+ *aRetargetEventDocument = nullptr;
+
+ // key and IME related events should not cross top level window boundary.
+ // Basically, such input events should be fired only on focused widget.
+ // However, some IMEs might need to clean up composition after focused
+ // window is deactivated. And also some tests on MozMill want to test key
+ // handling on deactivated window because MozMill window can be activated
+ // during tests. So, there is no merit the events should be redirected to
+ // active window. So, the events should be handled on the last focused
+ // content in the last focused DOM window in same top level window.
+ // Note, if no DOM window has been focused yet, we can discard the events.
+ if (aGUIEvent->IsTargetedAtFocusedWindow()) {
+ nsCOMPtr<nsPIDOMWindowOuter> window = GetFocusedDOMWindowInOurWindow();
+ // No DOM window in same top level window has not been focused yet,
+ // discard the events.
+ if (!window) {
+ return false;
+ }
+
+ RefPtr<Document> retargetEventDoc = window->GetExtantDoc();
+ if (!retargetEventDoc) {
+ return false;
+ }
+ retargetEventDoc.forget(aRetargetEventDocument);
+ return true;
+ }
+
+ nsIContent* capturingContent =
+ EventHandler::GetCapturingContentFor(aGUIEvent);
+ if (capturingContent) {
+ // if the mouse is being captured then retarget the mouse event at the
+ // document that is being captured.
+ RefPtr<Document> retargetEventDoc = capturingContent->GetComposedDoc();
+ retargetEventDoc.forget(aRetargetEventDocument);
+ return true;
+ }
+
+#ifdef ANDROID
+ if (aGUIEvent->mClass == eTouchEventClass ||
+ aGUIEvent->mClass == eMouseEventClass ||
+ aGUIEvent->mClass == eWheelEventClass) {
+ RefPtr<Document> retargetEventDoc = mPresShell->GetPrimaryContentDocument();
+ retargetEventDoc.forget(aRetargetEventDocument);
+ return true;
+ }
+#endif // #ifdef ANDROID
+
+ // When we don't find another document to handle the event, we need to keep
+ // handling the event by ourselves.
+ return true;
+}
+
+nsIFrame* PresShell::EventHandler::GetFrameForHandlingEventWith(
+ WidgetGUIEvent* aGUIEvent, Document* aRetargetDocument,
+ nsIFrame* aFrameForPresShell) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aRetargetDocument);
+
+ RefPtr<PresShell> retargetPresShell = aRetargetDocument->GetPresShell();
+ // Even if the document doesn't have PresShell, i.e., it's invisible, we
+ // need to dispatch only KeyboardEvent in its nearest visible document
+ // because key focus shouldn't be caught by invisible document.
+ if (!retargetPresShell) {
+ if (!aGUIEvent->HasKeyEventMessage()) {
+ return nullptr;
+ }
+ Document* retargetEventDoc = aRetargetDocument;
+ while (!retargetPresShell) {
+ retargetEventDoc = retargetEventDoc->GetInProcessParentDocument();
+ if (!retargetEventDoc) {
+ return nullptr;
+ }
+ retargetPresShell = retargetEventDoc->GetPresShell();
+ }
+ }
+
+ // If the found PresShell is this instance, caller needs to keep handling
+ // aGUIEvent by itself. Therefore, return the given frame which was set
+ // to aFrame of HandleEvent().
+ if (retargetPresShell == mPresShell) {
+ return aFrameForPresShell;
+ }
+
+ // Use root frame of the new PresShell if there is.
+ nsIFrame* rootFrame = retargetPresShell->GetRootFrame();
+ if (rootFrame) {
+ return rootFrame;
+ }
+
+ // Otherwise, and if aGUIEvent requires content of PresShell, caller should
+ // stop handling the event.
+ if (aGUIEvent->mMessage == eQueryTextContent ||
+ aGUIEvent->IsContentCommandEvent()) {
+ return nullptr;
+ }
+
+ // Otherwise, use nearest ancestor frame which includes the PresShell.
+ return GetNearestFrameContainingPresShell(retargetPresShell);
+}
+
+bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus, nsresult* aRv) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aEventStatus);
+ MOZ_ASSERT(aRv);
+
+ *aRv = NS_OK;
+
+ RefPtr<Document> retargetEventDoc;
+ if (!GetRetargetEventDocument(aGUIEvent, getter_AddRefs(retargetEventDoc))) {
+ // Nobody can handle this event. So, treat as handled by somebody to make
+ // caller do nothing anymore.
+ return true;
+ }
+
+ // If there is no proper retarget document, the caller should handle the
+ // event by itself.
+ if (!retargetEventDoc) {
+ return false;
+ }
+
+ nsIFrame* frame = GetFrameForHandlingEventWith(aGUIEvent, retargetEventDoc,
+ aFrameForPresShell);
+ if (!frame) {
+ // Nobody can handle this event. So, treat as handled by somebody to make
+ // caller do nothing anymore.
+ return true;
+ }
+
+ // If we reached same frame as set to HandleEvent(), the caller should handle
+ // the event by itself.
+ if (frame == aFrameForPresShell) {
+ return false;
+ }
+
+ // We need to handle aGUIEvent with another PresShell.
+ RefPtr<PresShell> presShell = frame->PresContext()->PresShell();
+ *aRv = presShell->HandleEvent(frame, aGUIEvent, true, aEventStatus);
+ return true;
+}
+
+bool PresShell::EventHandler::MaybeDiscardOrDelayKeyboardEvent(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+
+ if (aGUIEvent->mClass != eKeyboardEventClass) {
+ return false;
+ }
+
+ Document* document = GetDocument();
+ if (!document || !document->EventHandlingSuppressed()) {
+ return false;
+ }
+
+ MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent(),
+ !InputTaskManager::Get()->IsSuspended());
+
+ if (aGUIEvent->mMessage == eKeyDown) {
+ mPresShell->mNoDelayedKeyEvents = true;
+ } else if (!mPresShell->mNoDelayedKeyEvents) {
+ UniquePtr<DelayedKeyEvent> delayedKeyEvent =
+ MakeUnique<DelayedKeyEvent>(aGUIEvent->AsKeyboardEvent());
+ mPresShell->mDelayedEvents.AppendElement(std::move(delayedKeyEvent));
+ }
+ aGUIEvent->mFlags.mIsSuppressedOrDelayed = true;
+ return true;
+}
+
+bool PresShell::EventHandler::MaybeDiscardOrDelayMouseEvent(
+ nsIFrame* aFrameToHandleEvent, WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aFrameToHandleEvent);
+ MOZ_ASSERT(aGUIEvent);
+
+ if (aGUIEvent->mClass != eMouseEventClass) {
+ return false;
+ }
+
+ if (!aFrameToHandleEvent->PresContext()
+ ->Document()
+ ->EventHandlingSuppressed()) {
+ return false;
+ }
+
+ MOZ_ASSERT_IF(InputTaskManager::CanSuspendInputEvent() &&
+ aGUIEvent->mMessage != eMouseMove,
+ !InputTaskManager::Get()->IsSuspended());
+
+ RefPtr<PresShell> ps = aFrameToHandleEvent->PresShell();
+
+ if (aGUIEvent->mMessage == eMouseDown) {
+ ps->mNoDelayedMouseEvents = true;
+ } else if (!ps->mNoDelayedMouseEvents &&
+ (aGUIEvent->mMessage == eMouseUp ||
+ // contextmenu is triggered after right mouseup on Windows and
+ // right mousedown on other platforms.
+ aGUIEvent->mMessage == eContextMenu ||
+ aGUIEvent->mMessage == eMouseExitFromWidget)) {
+ UniquePtr<DelayedMouseEvent> delayedMouseEvent =
+ MakeUnique<DelayedMouseEvent>(aGUIEvent->AsMouseEvent());
+ ps->mDelayedEvents.AppendElement(std::move(delayedMouseEvent));
+ }
+
+ // If there is a suppressed event listener associated with the document,
+ // notify it about the suppressed mouse event. This allows devtools
+ // features to continue receiving mouse events even when the devtools
+ // debugger has paused execution in a page.
+ RefPtr<EventListener> suppressedListener = aFrameToHandleEvent->PresContext()
+ ->Document()
+ ->GetSuppressedEventListener();
+ if (!suppressedListener ||
+ aGUIEvent->AsMouseEvent()->mReason == WidgetMouseEvent::eSynthesized) {
+ return true;
+ }
+
+ nsCOMPtr<nsIContent> targetContent;
+ aFrameToHandleEvent->GetContentForEvent(aGUIEvent,
+ getter_AddRefs(targetContent));
+ if (targetContent) {
+ aGUIEvent->mTarget = targetContent;
+ }
+
+ nsCOMPtr<EventTarget> eventTarget = aGUIEvent->mTarget;
+ RefPtr<Event> event = EventDispatcher::CreateEvent(
+ eventTarget, aFrameToHandleEvent->PresContext(), aGUIEvent, u""_ns);
+
+ suppressedListener->HandleEvent(*event);
+ return true;
+}
+
+nsIFrame* PresShell::EventHandler::MaybeFlushThrottledStyles(
+ nsIFrame* aFrameForPresShell) {
+ if (!GetDocument()) {
+ // XXX Only when mPresShell has document, we'll try to look for a frame
+ // containing mPresShell even if given frame is nullptr. Does this
+ // make sense?
+ return aFrameForPresShell;
+ }
+
+ PresShell* rootPresShell = mPresShell->GetRootPresShell();
+ if (NS_WARN_IF(!rootPresShell)) {
+ return nullptr;
+ }
+ Document* rootDocument = rootPresShell->GetDocument();
+ if (NS_WARN_IF(!rootDocument)) {
+ return nullptr;
+ }
+
+ AutoWeakFrame weakFrameForPresShell(aFrameForPresShell);
+ { // scope for scriptBlocker.
+ nsAutoScriptBlocker scriptBlocker;
+ FlushThrottledStyles(*rootDocument);
+ }
+
+ if (weakFrameForPresShell.IsAlive()) {
+ return aFrameForPresShell;
+ }
+
+ return GetNearestFrameContainingPresShell(mPresShell);
+}
+
+nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEvent(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored,
+ bool* aIsCaptureRetargeted) {
+ MOZ_ASSERT(aFrameForPresShell);
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aIsCapturingContentIgnored);
+ MOZ_ASSERT(aIsCaptureRetargeted);
+
+ nsIFrame* rootFrameToHandleEvent = ComputeRootFrameToHandleEventWithPopup(
+ aFrameForPresShell, aGUIEvent, aCapturingContent,
+ aIsCapturingContentIgnored);
+ if (*aIsCapturingContentIgnored) {
+ // If the capturing content is ignored, we don't need to respect it.
+ return rootFrameToHandleEvent;
+ }
+
+ if (!aCapturingContent) {
+ return rootFrameToHandleEvent;
+ }
+
+ // If we have capturing content, let's compute root frame with it again.
+ return ComputeRootFrameToHandleEventWithCapturingContent(
+ rootFrameToHandleEvent, aCapturingContent, aIsCapturingContentIgnored,
+ aIsCaptureRetargeted);
+}
+
+nsIFrame* PresShell::EventHandler::ComputeRootFrameToHandleEventWithPopup(
+ nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored) {
+ MOZ_ASSERT(aRootFrameToHandleEvent);
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aIsCapturingContentIgnored);
+
+ *aIsCapturingContentIgnored = false;
+
+ nsPresContext* framePresContext = aRootFrameToHandleEvent->PresContext();
+ nsPresContext* rootPresContext = framePresContext->GetRootPresContext();
+ NS_ASSERTION(rootPresContext == GetPresContext()->GetRootPresContext(),
+ "How did we end up outside the connected "
+ "prescontext/viewmanager hierarchy?");
+ nsIFrame* popupFrame = nsLayoutUtils::GetPopupFrameForEventCoordinates(
+ rootPresContext, aGUIEvent);
+ if (!popupFrame) {
+ return aRootFrameToHandleEvent;
+ }
+
+ // If a remote browser is currently capturing input break out if we
+ // detect a chrome generated popup.
+ // XXXedgar, do we need to check fission OOP iframe?
+ if (aCapturingContent &&
+ EventStateManager::IsTopLevelRemoteTarget(aCapturingContent)) {
+ *aIsCapturingContentIgnored = true;
+ }
+
+ // If the popupFrame is an ancestor of the 'frame', the frame should
+ // handle the event, otherwise, the popup should handle it.
+ if (nsContentUtils::ContentIsCrossDocDescendantOf(
+ framePresContext->GetPresShell()->GetDocument(),
+ popupFrame->GetContent())) {
+ return aRootFrameToHandleEvent;
+ }
+
+ // If we aren't starting our event dispatch from the root frame of the
+ // root prescontext, then someone must be capturing the mouse. In that
+ // case we only want to use the popup list if the capture is
+ // inside the popup.
+ if (framePresContext == rootPresContext &&
+ aRootFrameToHandleEvent == FrameConstructor()->GetRootFrame()) {
+ return popupFrame;
+ }
+
+ if (aCapturingContent && !*aIsCapturingContentIgnored &&
+ aCapturingContent->IsInclusiveDescendantOf(popupFrame->GetContent())) {
+ return popupFrame;
+ }
+
+ return aRootFrameToHandleEvent;
+}
+
+nsIFrame*
+PresShell::EventHandler::ComputeRootFrameToHandleEventWithCapturingContent(
+ nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
+ bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted) {
+ MOZ_ASSERT(aRootFrameToHandleEvent);
+ MOZ_ASSERT(aCapturingContent);
+ MOZ_ASSERT(aIsCapturingContentIgnored);
+ MOZ_ASSERT(aIsCaptureRetargeted);
+
+ *aIsCapturingContentIgnored = false;
+ *aIsCaptureRetargeted = false;
+
+ // If a capture is active, determine if the BrowsingContext is active. If
+ // not, clear the capture and target the mouse event normally instead. This
+ // would occur if the mouse button is held down while a tab change occurs.
+ // If the BrowsingContext is active, look for a scrolling container.
+ BrowsingContext* bc = GetPresContext()->Document()->GetBrowsingContext();
+ if (!bc || !bc->IsActive()) {
+ ClearMouseCapture();
+ *aIsCapturingContentIgnored = true;
+ return aRootFrameToHandleEvent;
+ }
+
+ if (PresShell::sCapturingContentInfo.mRetargetToElement) {
+ *aIsCaptureRetargeted = true;
+ return aRootFrameToHandleEvent;
+ }
+
+ // A check was already done above to ensure that aCapturingContent is
+ // in this presshell.
+ NS_ASSERTION(aCapturingContent->OwnerDoc() == GetDocument(),
+ "Unexpected document");
+ nsIFrame* captureFrame = aCapturingContent->GetPrimaryFrame();
+ if (!captureFrame) {
+ return aRootFrameToHandleEvent;
+ }
+
+ // scrollable frames should use the scrolling container as the root instead
+ // of the document
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(captureFrame);
+ return scrollFrame ? scrollFrame->GetScrolledFrame()
+ : aRootFrameToHandleEvent;
+}
+
+nsresult
+PresShell::EventHandler::HandleEventWithPointerCapturingContentWithoutItsFrame(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aPointerCapturingContent);
+ MOZ_ASSERT(!aPointerCapturingContent->GetPrimaryFrame(),
+ "Handle the event with frame rather than only with the content");
+ MOZ_ASSERT(aEventStatus);
+
+ RefPtr<PresShell> presShellForCapturingContent =
+ PresShell::GetShellForEventTarget(nullptr, aPointerCapturingContent);
+ if (!presShellForCapturingContent) {
+ // If we can't process event for the capturing content, release
+ // the capture.
+ PointerEventHandler::ReleaseIfCaptureByDescendant(aPointerCapturingContent);
+ // Since we don't dispatch ePointeUp nor ePointerCancel in this case,
+ // EventStateManager::PostHandleEvent does not have a chance to dispatch
+ // ePointerLostCapture event. Therefore, we need to dispatch it here.
+ PointerEventHandler::MaybeImplicitlyReleasePointerCapture(aGUIEvent);
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIContent> overrideClickTarget =
+ GetOverrideClickTarget(aGUIEvent, aFrameForPresShell);
+
+ // Dispatch events to the capturing content even it's frame is
+ // destroyed.
+ PointerEventHandler::DispatchPointerFromMouseOrTouch(
+ presShellForCapturingContent, nullptr, aPointerCapturingContent,
+ aGUIEvent, false, aEventStatus, nullptr);
+
+ if (presShellForCapturingContent == mPresShell) {
+ return HandleEventWithTarget(aGUIEvent, nullptr, aPointerCapturingContent,
+ aEventStatus, true, nullptr,
+ overrideClickTarget);
+ }
+
+ EventHandler eventHandlerForCapturingContent(
+ std::move(presShellForCapturingContent));
+ return eventHandlerForCapturingContent.HandleEventWithTarget(
+ aGUIEvent, nullptr, aPointerCapturingContent, aEventStatus, true, nullptr,
+ overrideClickTarget);
+}
+
+nsresult PresShell::EventHandler::HandleEventAtFocusedContent(
+ WidgetGUIEvent* aGUIEvent, nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
+ MOZ_ASSERT(aEventStatus);
+
+ AutoCurrentEventInfoSetter eventInfoSetter(*this);
+
+ RefPtr<Element> eventTargetElement =
+ ComputeFocusedEventTargetElement(aGUIEvent);
+
+ mPresShell->mCurrentEventFrame = nullptr;
+ if (eventTargetElement) {
+ nsresult rv = NS_OK;
+ if (MaybeHandleEventWithAnotherPresShell(eventTargetElement, aGUIEvent,
+ aEventStatus, &rv)) {
+ return rv;
+ }
+ }
+
+ // If we cannot handle the event with mPresShell, let's try to handle it
+ // with parent PresShell.
+ mPresShell->mCurrentEventContent = eventTargetElement;
+ if (!mPresShell->GetCurrentEventContent() ||
+ !mPresShell->GetCurrentEventFrame() ||
+ InZombieDocument(mPresShell->mCurrentEventContent)) {
+ return RetargetEventToParent(aGUIEvent, aEventStatus);
+ }
+
+ nsresult rv =
+ HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
+ return rv;
+}
+
+Element* PresShell::EventHandler::ComputeFocusedEventTargetElement(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aGUIEvent->IsTargetedAtFocusedContent());
+
+ // key and IME related events go to the focused frame in this DOM window.
+ nsPIDOMWindowOuter* window = GetDocument()->GetWindow();
+ nsCOMPtr<nsPIDOMWindowOuter> focusedWindow;
+ Element* eventTargetElement = nsFocusManager::GetFocusedDescendant(
+ window, nsFocusManager::eOnlyCurrentWindow,
+ getter_AddRefs(focusedWindow));
+
+ // otherwise, if there is no focused content or the focused content has
+ // no frame, just use the root content. This ensures that key events
+ // still get sent to the window properly if nothing is focused or if a
+ // frame goes away while it is focused.
+ if (!eventTargetElement || !eventTargetElement->GetPrimaryFrame()) {
+ eventTargetElement = GetDocument()->GetUnfocusedKeyEventTarget();
+ }
+
+ switch (aGUIEvent->mMessage) {
+ case eKeyDown:
+ sLastKeyDownEventTargetElement = eventTargetElement;
+ return eventTargetElement;
+ case eKeyPress:
+ case eKeyUp:
+ if (!sLastKeyDownEventTargetElement) {
+ return eventTargetElement;
+ }
+ // If a different element is now focused for the keypress/keyup event
+ // than what was focused during the keydown event, check if the new
+ // focused element is not in a chrome document any more, and if so,
+ // retarget the event back at the keydown target. This prevents a
+ // content area from grabbing the focus from chrome in-between key
+ // events.
+ if (eventTargetElement) {
+ bool keyDownIsChrome = nsContentUtils::IsChromeDoc(
+ sLastKeyDownEventTargetElement->GetComposedDoc());
+ if (keyDownIsChrome != nsContentUtils::IsChromeDoc(
+ eventTargetElement->GetComposedDoc()) ||
+ (keyDownIsChrome && BrowserParent::GetFrom(eventTargetElement))) {
+ eventTargetElement = sLastKeyDownEventTargetElement;
+ }
+ }
+
+ if (aGUIEvent->mMessage == eKeyUp) {
+ sLastKeyDownEventTargetElement = nullptr;
+ }
+ [[fallthrough]];
+ default:
+ return eventTargetElement;
+ }
+}
+
+bool PresShell::EventHandler::MaybeHandleEventWithAnotherPresShell(
+ Element* aEventTargetElement, WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus, nsresult* aRv) {
+ MOZ_ASSERT(aEventTargetElement);
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
+ MOZ_ASSERT(aEventStatus);
+ MOZ_ASSERT(aRv);
+
+ Document* eventTargetDocument = aEventTargetElement->OwnerDoc();
+ if (!eventTargetDocument || eventTargetDocument == GetDocument()) {
+ *aRv = NS_OK;
+ return false;
+ }
+
+ RefPtr<PresShell> eventTargetPresShell = eventTargetDocument->GetPresShell();
+ if (!eventTargetPresShell) {
+ *aRv = NS_OK;
+ return true; // No PresShell can handle the event.
+ }
+
+ EventHandler eventHandler(std::move(eventTargetPresShell));
+ *aRv = eventHandler.HandleRetargetedEvent(aGUIEvent, aEventStatus,
+ aEventTargetElement);
+ return true;
+}
+
+nsresult PresShell::EventHandler::HandleEventWithFrameForPresShell(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(!aGUIEvent->IsUsingCoordinates());
+ MOZ_ASSERT(!aGUIEvent->IsTargetedAtFocusedContent());
+ MOZ_ASSERT(aEventStatus);
+
+ AutoCurrentEventInfoSetter eventInfoSetter(*this, aFrameForPresShell,
+ nullptr);
+
+ nsresult rv = NS_OK;
+ if (mPresShell->GetCurrentEventFrame()) {
+ rv =
+ HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true, nullptr);
+ }
+
+ return rv;
+}
+
+Document* PresShell::GetPrimaryContentDocument() {
+ nsPresContext* context = GetPresContext();
+ if (!context || !context->IsRoot()) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> shellAsTreeItem = context->GetDocShell();
+ if (!shellAsTreeItem) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> owner;
+ shellAsTreeItem->GetTreeOwner(getter_AddRefs(owner));
+ if (!owner) {
+ return nullptr;
+ }
+
+ // now get the primary content shell (active tab)
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ owner->GetPrimaryContentShell(getter_AddRefs(item));
+ nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(item);
+ if (!childDocShell) {
+ return nullptr;
+ }
+
+ return childDocShell->GetExtantDocument();
+}
+
+nsresult PresShell::EventHandler::HandleEventWithTarget(
+ WidgetEvent* aEvent, nsIFrame* aNewEventFrame, nsIContent* aNewEventContent,
+ nsEventStatus* aEventStatus, bool aIsHandlingNativeEvent,
+ nsIContent** aTargetContent, nsIContent* aOverrideClickTarget) {
+ MOZ_ASSERT(aEvent);
+ MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
+
+#if DEBUG
+ MOZ_ASSERT(!aNewEventFrame ||
+ aNewEventFrame->PresContext()->GetPresShell() == mPresShell,
+ "wrong shell");
+ if (aNewEventContent) {
+ Document* doc = aNewEventContent->GetComposedDoc();
+ NS_ASSERTION(doc, "event for content that isn't in a document");
+ // NOTE: We don't require that the document still have a PresShell.
+ // See bug 1375940.
+ }
+#endif
+ NS_ENSURE_STATE(!aNewEventContent ||
+ aNewEventContent->GetComposedDoc() == GetDocument());
+ if (aEvent->mClass == ePointerEventClass) {
+ mPresShell->RecordPointerLocation(aEvent->AsMouseEvent());
+ }
+ AutoPointerEventTargetUpdater updater(mPresShell, aEvent, aNewEventFrame,
+ aTargetContent);
+ AutoCurrentEventInfoSetter eventInfoSetter(*this, aNewEventFrame,
+ aNewEventContent);
+ nsresult rv = HandleEventWithCurrentEventInfo(aEvent, aEventStatus, false,
+ aOverrideClickTarget);
+ return rv;
+}
+
+namespace {
+
+class MOZ_RAII AutoEventHandler final {
+ public:
+ AutoEventHandler(WidgetEvent* aEvent, Document* aDocument) : mEvent(aEvent) {
+ MOZ_ASSERT(mEvent);
+ MOZ_ASSERT(mEvent->IsTrusted());
+
+ if (mEvent->mMessage == eMouseDown) {
+ PresShell::ReleaseCapturingContent();
+ PresShell::AllowMouseCapture(true);
+ }
+ if (NeedsToUpdateCurrentMouseBtnState()) {
+ WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
+ if (mouseEvent) {
+ EventStateManager::sCurrentMouseBtn = mouseEvent->mButton;
+ }
+ }
+ }
+
+ ~AutoEventHandler() {
+ if (mEvent->mMessage == eMouseDown) {
+ PresShell::AllowMouseCapture(false);
+ }
+ if (NeedsToUpdateCurrentMouseBtnState()) {
+ EventStateManager::sCurrentMouseBtn = MouseButton::eNotPressed;
+ }
+ }
+
+ protected:
+ bool NeedsToUpdateCurrentMouseBtnState() const {
+ return mEvent->mMessage == eMouseDown || mEvent->mMessage == eMouseUp ||
+ mEvent->mMessage == ePointerDown || mEvent->mMessage == ePointerUp;
+ }
+
+ WidgetEvent* mEvent;
+};
+
+} // anonymous namespace
+
+nsresult PresShell::EventHandler::HandleEventWithCurrentEventInfo(
+ WidgetEvent* aEvent, nsEventStatus* aEventStatus,
+ bool aIsHandlingNativeEvent, nsIContent* aOverrideClickTarget) {
+ MOZ_ASSERT(aEvent);
+ MOZ_ASSERT(aEventStatus);
+
+ RefPtr<EventStateManager> manager = GetPresContext()->EventStateManager();
+
+ // If we cannot handle the event with mPresShell because of no target,
+ // just record the response time.
+ // XXX Is this intentional? In such case, the score is really good because
+ // of nothing to do. So, it may make average and median better.
+ if (NS_EVENT_NEEDS_FRAME(aEvent) && !mPresShell->GetCurrentEventFrame() &&
+ !mPresShell->GetCurrentEventContent()) {
+ RecordEventHandlingResponsePerformance(aEvent);
+ return NS_OK;
+ }
+
+ if (mPresShell->mCurrentEventContent && aEvent->IsTargetedAtFocusedWindow() &&
+ aEvent->AllowFlushingPendingNotifications()) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ // This may run script now. So, mPresShell might be destroyed after here.
+ nsCOMPtr<nsIContent> currentEventContent =
+ mPresShell->mCurrentEventContent;
+ fm->FlushBeforeEventHandlingIfNeeded(currentEventContent);
+ }
+ }
+
+ bool touchIsNew = false;
+ if (!PrepareToDispatchEvent(aEvent, aEventStatus, &touchIsNew)) {
+ return NS_OK;
+ }
+
+ // We finished preparing to dispatch the event. So, let's record the
+ // performance.
+ RecordEventPreparationPerformance(aEvent);
+
+ AutoHandlingUserInputStatePusher userInpStatePusher(
+ UserActivation::IsUserInteractionEvent(aEvent), aEvent);
+ AutoEventHandler eventHandler(aEvent, GetDocument());
+ AutoPopupStatePusher popupStatePusher(
+ PopupBlocker::GetEventPopupControlState(aEvent));
+
+ // FIXME. If the event was reused, we need to clear the old target,
+ // bug 329430
+ aEvent->mTarget = nullptr;
+
+ HandlingTimeAccumulator handlingTimeAccumulator(*this, aEvent);
+
+ nsresult rv = DispatchEvent(manager, aEvent, touchIsNew, aEventStatus,
+ aOverrideClickTarget);
+
+ if (!mPresShell->IsDestroying() && aIsHandlingNativeEvent) {
+ // Ensure that notifications to IME should be sent before getting next
+ // native event from the event queue.
+ // XXX Should we check the event message or event class instead of
+ // using aIsHandlingNativeEvent?
+ manager->TryToFlushPendingNotificationsToIME();
+ }
+
+ FinalizeHandlingEvent(aEvent);
+
+ RecordEventHandlingResponsePerformance(aEvent);
+
+ return rv; // Result of DispatchEvent()
+}
+
+nsresult PresShell::EventHandler::DispatchEvent(
+ EventStateManager* aEventStateManager, WidgetEvent* aEvent,
+ bool aTouchIsNew, nsEventStatus* aEventStatus,
+ nsIContent* aOverrideClickTarget) {
+ MOZ_ASSERT(aEventStateManager);
+ MOZ_ASSERT(aEvent);
+ MOZ_ASSERT(aEventStatus);
+
+ // 1. Give event to event manager for pre event state changes and
+ // generation of synthetic events.
+ { // Scope for presContext
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ nsCOMPtr<nsIContent> eventContent = mPresShell->mCurrentEventContent;
+ nsresult rv = aEventStateManager->PreHandleEvent(
+ presContext, aEvent, mPresShell->mCurrentEventFrame, eventContent,
+ aEventStatus, aOverrideClickTarget);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ }
+
+ // 2. Give event to the DOM for third party and JS use.
+ bool wasHandlingKeyBoardEvent = nsContentUtils::IsHandlingKeyBoardEvent();
+ if (aEvent->mClass == eKeyboardEventClass) {
+ nsContentUtils::SetIsHandlingKeyBoardEvent(true);
+ }
+ // If EventStateManager or something wants reply from remote process and
+ // needs to win any other event listeners in chrome, the event is both
+ // stopped its propagation and marked as "waiting reply from remote
+ // process". In this case, PresShell shouldn't dispatch the event into
+ // the DOM tree because they don't have a chance to stop propagation in
+ // the system event group. On the other hand, if its propagation is not
+ // stopped, that means that the event may be reserved by chrome. If it's
+ // reserved by chrome, the event shouldn't be sent to any remote
+ // processes. In this case, PresShell needs to dispatch the event to
+ // the DOM tree for checking if it's reserved.
+ if (aEvent->IsAllowedToDispatchDOMEvent() &&
+ !(aEvent->PropagationStopped() &&
+ aEvent->IsWaitingReplyFromRemoteProcess())) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
+ "Somebody changed aEvent to cause a DOM event!");
+ nsPresShellEventCB eventCB(mPresShell);
+ if (nsIFrame* target = mPresShell->GetCurrentEventFrame()) {
+ if (target->OnlySystemGroupDispatch(aEvent->mMessage)) {
+ aEvent->StopPropagation();
+ }
+ }
+ if (aEvent->mClass == eTouchEventClass) {
+ DispatchTouchEventToDOM(aEvent, aEventStatus, &eventCB, aTouchIsNew);
+ } else {
+ DispatchEventToDOM(aEvent, aEventStatus, &eventCB);
+ }
+ }
+
+ nsContentUtils::SetIsHandlingKeyBoardEvent(wasHandlingKeyBoardEvent);
+
+ if (mPresShell->IsDestroying()) {
+ return NS_OK;
+ }
+
+ // 3. Give event to event manager for post event state changes and
+ // generation of synthetic events.
+ // Refetch the prescontext, in case it changed.
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ return aEventStateManager->PostHandleEvent(
+ presContext, aEvent, mPresShell->GetCurrentEventFrame(), aEventStatus,
+ aOverrideClickTarget);
+}
+
+bool PresShell::EventHandler::PrepareToDispatchEvent(
+ WidgetEvent* aEvent, nsEventStatus* aEventStatus, bool* aTouchIsNew) {
+ MOZ_ASSERT(aEvent->IsTrusted());
+ MOZ_ASSERT(aEventStatus);
+ MOZ_ASSERT(aTouchIsNew);
+
+ *aTouchIsNew = false;
+ if (aEvent->IsUserAction()) {
+ mPresShell->mHasHandledUserInput = true;
+ }
+
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp: {
+ WidgetKeyboardEvent* keyboardEvent = aEvent->AsKeyboardEvent();
+ MaybeHandleKeyboardEventBeforeDispatch(keyboardEvent);
+ return true;
+ }
+ case eMouseMove: {
+ bool allowCapture = EventStateManager::GetActiveEventStateManager() &&
+ GetPresContext() &&
+ GetPresContext()->EventStateManager() ==
+ EventStateManager::GetActiveEventStateManager();
+ PresShell::AllowMouseCapture(allowCapture);
+ return true;
+ }
+ case eDrop: {
+ nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
+ if (session) {
+ bool onlyChromeDrop = false;
+ session->GetOnlyChromeDrop(&onlyChromeDrop);
+ if (onlyChromeDrop) {
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ }
+ }
+ return true;
+ }
+ case eDragExit: {
+ if (!StaticPrefs::dom_event_dragexit_enabled()) {
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ }
+ return true;
+ }
+ case eContextMenu: {
+ // If we cannot open context menu even though eContextMenu is fired, we
+ // should stop dispatching it into the DOM.
+ WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
+ if (mouseEvent->IsContextMenuKeyEvent() &&
+ !AdjustContextMenuKeyEvent(mouseEvent)) {
+ return false;
+ }
+
+ // If "Shift" state is active, context menu should be forcibly opened even
+ // if web apps want to prevent it since we respect our users' intention.
+ // In this case, we don't fire "contextmenu" event on web content because
+ // of not cancelable.
+ if (mouseEvent->IsShift() &&
+ StaticPrefs::dom_event_contextmenu_shift_suppresses_event()) {
+ aEvent->mFlags.mOnlyChromeDispatch = true;
+ aEvent->mFlags.mRetargetToNonNativeAnonymous = true;
+ }
+ return true;
+ }
+ case eTouchStart:
+ case eTouchMove:
+ case eTouchEnd:
+ case eTouchCancel:
+ case eTouchPointerCancel:
+ return mPresShell->mTouchManager.PreHandleEvent(
+ aEvent, aEventStatus, *aTouchIsNew, mPresShell->mCurrentEventContent);
+ default:
+ return true;
+ }
+}
+
+void PresShell::EventHandler::FinalizeHandlingEvent(WidgetEvent* aEvent) {
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp: {
+ if (aEvent->AsKeyboardEvent()->mKeyCode == NS_VK_ESCAPE) {
+ if (aEvent->mMessage == eKeyUp) {
+ // Reset this flag after key up is handled.
+ mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = false;
+ } else {
+ if (aEvent->mFlags.mOnlyChromeDispatch &&
+ aEvent->mFlags.mDefaultPreventedByChrome) {
+ mPresShell->mIsLastChromeOnlyEscapeKeyConsumed = true;
+ }
+ if (aEvent->mMessage == eKeyDown &&
+ !aEvent->mFlags.mDefaultPrevented) {
+ if (RefPtr<Document> doc = GetDocument()) {
+ doc->HandleEscKey();
+ }
+ }
+ }
+ }
+ if (aEvent->mMessage == eKeyDown) {
+ mPresShell->mIsLastKeyDownCanceled = aEvent->mFlags.mDefaultPrevented;
+ }
+ return;
+ }
+ case eMouseUp:
+ // reset the capturing content now that the mouse button is up
+ PresShell::ReleaseCapturingContent();
+ return;
+ case eMouseMove:
+ PresShell::AllowMouseCapture(false);
+ return;
+ case eDrag:
+ case eDragEnd:
+ case eDragEnter:
+ case eDragExit:
+ case eDragLeave:
+ case eDragOver:
+ case eDrop: {
+ // After any drag event other than dragstart (which is handled
+ // separately, as we need to collect the data first), the DataTransfer
+ // needs to be made protected, and then disconnected.
+ DataTransfer* dataTransfer = aEvent->AsDragEvent()->mDataTransfer;
+ if (dataTransfer) {
+ dataTransfer->Disconnect();
+ }
+ return;
+ }
+ default:
+ return;
+ }
+}
+
+void PresShell::EventHandler::MaybeHandleKeyboardEventBeforeDispatch(
+ WidgetKeyboardEvent* aKeyboardEvent) {
+ MOZ_ASSERT(aKeyboardEvent);
+
+ if (aKeyboardEvent->mKeyCode != NS_VK_ESCAPE) {
+ return;
+ }
+
+ // If we're in fullscreen mode, exit from it forcibly when Escape key is
+ // pressed.
+ Document* doc = mPresShell->GetCurrentEventContent()
+ ? mPresShell->mCurrentEventContent->OwnerDoc()
+ : nullptr;
+ Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
+ if (root && root->GetFullscreenElement()) {
+ // Prevent default action on ESC key press when exiting
+ // DOM fullscreen mode. This prevents the browser ESC key
+ // handler from stopping all loads in the document, which
+ // would cause <video> loads to stop.
+ // XXX We need to claim the Escape key event which will be
+ // dispatched only into chrome is already consumed by
+ // content because we need to prevent its default here
+ // for some reasons (not sure) but we need to detect
+ // if a chrome event handler will call PreventDefault()
+ // again and check it later.
+ aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
+ aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
+
+ // The event listeners in chrome can prevent this ESC behavior by
+ // calling prevent default on the preceding keydown/press events.
+ if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed &&
+ aKeyboardEvent->mMessage == eKeyUp) {
+ // ESC key released while in DOM fullscreen mode.
+ // Fully exit all browser windows and documents from
+ // fullscreen mode.
+ Document::AsyncExitFullscreen(nullptr);
+ }
+ }
+
+ nsCOMPtr<Document> pointerLockedDoc = PointerLockManager::GetLockedDocument();
+ if (!mPresShell->mIsLastChromeOnlyEscapeKeyConsumed && pointerLockedDoc) {
+ // XXX See above comment to understand the reason why this needs
+ // to claim that the Escape key event is consumed by content
+ // even though it will be dispatched only into chrome.
+ aKeyboardEvent->PreventDefaultBeforeDispatch(CrossProcessForwarding::eStop);
+ aKeyboardEvent->mFlags.mOnlyChromeDispatch = true;
+ if (aKeyboardEvent->mMessage == eKeyUp) {
+ PointerLockManager::Unlock();
+ }
+ }
+}
+
+void PresShell::EventHandler::RecordEventPreparationPerformance(
+ const WidgetEvent* aEvent) {
+ MOZ_ASSERT(aEvent);
+
+ switch (aEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp:
+ if (aEvent->AsKeyboardEvent()->ShouldInteractionTimeRecorded()) {
+ GetPresContext()->RecordInteractionTime(
+ nsPresContext::InteractionType::KeyInteraction, aEvent->mTimeStamp);
+ }
+ Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_KEYBOARD_MS,
+ aEvent->mTimeStamp);
+ return;
+
+ case eMouseDown:
+ case eMouseUp:
+ Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_QUEUED_CLICK_MS,
+ aEvent->mTimeStamp);
+ [[fallthrough]];
+ case ePointerDown:
+ case ePointerUp:
+ GetPresContext()->RecordInteractionTime(
+ nsPresContext::InteractionType::ClickInteraction, aEvent->mTimeStamp);
+ return;
+
+ case eMouseMove:
+ if (aEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_QUEUED_APZ_MOUSE_MOVE_MS,
+ aEvent->mTimeStamp);
+ }
+ GetPresContext()->RecordInteractionTime(
+ nsPresContext::InteractionType::MouseMoveInteraction,
+ aEvent->mTimeStamp);
+ return;
+
+ case eWheel:
+ if (aEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_QUEUED_APZ_WHEEL_MS, aEvent->mTimeStamp);
+ }
+ return;
+
+ case eTouchMove:
+ if (aEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_QUEUED_APZ_TOUCH_MOVE_MS,
+ aEvent->mTimeStamp);
+ }
+ return;
+
+ default:
+ return;
+ }
+}
+
+void PresShell::EventHandler::RecordEventHandlingResponsePerformance(
+ const WidgetEvent* aEvent) {
+ if (!Telemetry::CanRecordBase() || aEvent->mTimeStamp.IsNull() ||
+ aEvent->mTimeStamp <= mPresShell->mLastOSWake ||
+ !aEvent->AsInputEvent()) {
+ return;
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ double millis = (now - aEvent->mTimeStamp).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
+ if (GetDocument() &&
+ GetDocument()->GetReadyStateEnum() != Document::READYSTATE_COMPLETE) {
+ Telemetry::Accumulate(Telemetry::LOAD_INPUT_EVENT_RESPONSE_MS, millis);
+ }
+
+ if (!sLastInputProcessed || sLastInputProcessed < aEvent->mTimeStamp) {
+ if (sLastInputProcessed) {
+ // This input event was created after we handled the last one.
+ // Accumulate the previous events' coalesced duration.
+ double lastMillis =
+ (sLastInputProcessed - sLastInputCreated).ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_COALESCED_MS,
+ lastMillis);
+
+ if (MOZ_UNLIKELY(!PresShell::sProcessInteractable)) {
+ // For content process, we use the ready state of
+ // top-level-content-document to know if the process has finished the
+ // start-up.
+ // For parent process, see the topic
+ // 'sessionstore-one-or-no-tab-restored' in PresShell::Observe.
+ if (XRE_IsContentProcess() && GetDocument() &&
+ GetDocument()->IsTopLevelContentDocument()) {
+ switch (GetDocument()->GetReadyStateEnum()) {
+ case Document::READYSTATE_INTERACTIVE:
+ case Document::READYSTATE_COMPLETE:
+ PresShell::sProcessInteractable = true;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if (MOZ_LIKELY(PresShell::sProcessInteractable)) {
+ Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_POST_STARTUP_MS,
+ lastMillis);
+ } else {
+ Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_STARTUP_MS,
+ lastMillis);
+ }
+ }
+ sLastInputCreated = aEvent->mTimeStamp;
+ } else if (aEvent->mTimeStamp < sLastInputCreated) {
+ // This event was created before the last input. May be processing out
+ // of order, so coalesce backwards, too.
+ sLastInputCreated = aEvent->mTimeStamp;
+ }
+ sLastInputProcessed = now;
+}
+
+// static
+nsIPrincipal*
+PresShell::EventHandler::GetDocumentPrincipalToCompareWithBlacklist(
+ PresShell& aPresShell) {
+ nsPresContext* presContext = aPresShell.GetPresContext();
+ if (NS_WARN_IF(!presContext)) {
+ return nullptr;
+ }
+ return presContext->Document()->GetPrincipalForPrefBasedHacks();
+}
+
+nsresult PresShell::EventHandler::DispatchEventToDOM(
+ WidgetEvent* aEvent, nsEventStatus* aEventStatus,
+ nsPresShellEventCB* aEventCB) {
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsINode> eventTarget = mPresShell->mCurrentEventContent;
+ nsPresShellEventCB* eventCBPtr = aEventCB;
+ if (!eventTarget) {
+ nsCOMPtr<nsIContent> targetContent;
+ if (mPresShell->mCurrentEventFrame) {
+ rv = mPresShell->mCurrentEventFrame->GetContentForEvent(
+ aEvent, getter_AddRefs(targetContent));
+ }
+ if (NS_SUCCEEDED(rv) && targetContent) {
+ eventTarget = targetContent;
+ } else if (GetDocument()) {
+ eventTarget = GetDocument();
+ // If we don't have any content, the callback wouldn't probably
+ // do nothing.
+ eventCBPtr = nullptr;
+ }
+ }
+ if (eventTarget) {
+ if (eventTarget->OwnerDoc()->ShouldResistFingerprinting(
+ RFPTarget::WidgetEvents) &&
+ aEvent->IsBlockedForFingerprintingResistance()) {
+ aEvent->mFlags.mOnlySystemGroupDispatchInContent = true;
+ } else if (aEvent->mMessage == eKeyPress) {
+ // If eKeyPress event is marked as not dispatched in the default event
+ // group in web content, it's caused by non-printable key or key
+ // combination. In this case, UI Events declares that browsers
+ // shouldn't dispatch keypress event. However, some web apps may be
+ // broken with this strict behavior due to historical issue.
+ // Therefore, we need to keep dispatching keypress event for such keys
+ // even with breaking the standard.
+ // Similarly, the other browsers sets non-zero value of keyCode or
+ // charCode of keypress event to the other. Therefore, we should
+ // behave so, however, some web apps may be broken. On such web apps,
+ // we should keep using legacy our behavior.
+ if (!mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist) {
+ mPresShell->mInitializedWithKeyPressEventDispatchingBlacklist = true;
+ nsCOMPtr<nsIPrincipal> principal =
+ GetDocumentPrincipalToCompareWithBlacklist(*mPresShell);
+ if (principal) {
+ mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys =
+ principal->IsURIInPrefList(
+ "dom.keyboardevent.keypress.hack.dispatch_non_printable_"
+ "keys") ||
+ principal->IsURIInPrefList(
+ "dom.keyboardevent.keypress.hack."
+ "dispatch_non_printable_keys.addl");
+
+ mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues |=
+ principal->IsURIInPrefList(
+ "dom.keyboardevent.keypress.hack."
+ "use_legacy_keycode_and_charcode") ||
+ principal->IsURIInPrefList(
+ "dom.keyboardevent.keypress.hack."
+ "use_legacy_keycode_and_charcode.addl");
+ }
+ }
+ if (mPresShell->mForceDispatchKeyPressEventsForNonPrintableKeys) {
+ aEvent->mFlags.mOnlySystemGroupDispatchInContent = false;
+ }
+ if (mPresShell->mForceUseLegacyKeyCodeAndCharCodeValues) {
+ aEvent->AsKeyboardEvent()->mUseLegacyKeyCodeAndCharCodeValues = true;
+ }
+ }
+
+ if (aEvent->mClass == eCompositionEventClass) {
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ RefPtr<BrowserParent> browserParent =
+ IMEStateManager::GetActiveBrowserParent();
+ IMEStateManager::DispatchCompositionEvent(
+ eventTarget, presContext, browserParent, aEvent->AsCompositionEvent(),
+ aEventStatus, eventCBPtr);
+ } else {
+ if (aEvent->mClass == eMouseEventClass) {
+ PresShell::sMouseButtons = aEvent->AsMouseEvent()->mButtons;
+ }
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ EventDispatcher::Dispatch(eventTarget, presContext, aEvent, nullptr,
+ aEventStatus, eventCBPtr);
+ }
+ }
+ return rv;
+}
+
+void PresShell::EventHandler::DispatchTouchEventToDOM(
+ WidgetEvent* aEvent, nsEventStatus* aEventStatus,
+ nsPresShellEventCB* aEventCB, bool aTouchIsNew) {
+ // calling preventDefault on touchstart or the first touchmove for a
+ // point prevents mouse events. calling it on the touchend should
+ // prevent click dispatching.
+ bool canPrevent = (aEvent->mMessage == eTouchStart) ||
+ (aEvent->mMessage == eTouchMove && aTouchIsNew) ||
+ (aEvent->mMessage == eTouchEnd);
+ bool preventDefault = false;
+ nsEventStatus tmpStatus = nsEventStatus_eIgnore;
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+
+ // loop over all touches and dispatch events on any that have changed
+ for (dom::Touch* touch : touchEvent->mTouches) {
+ // We should remove all suppressed touch instances in
+ // TouchManager::PreHandleEvent.
+ MOZ_ASSERT(!touch->mIsTouchEventSuppressed);
+
+ if (!touch || !touch->mChanged) {
+ continue;
+ }
+
+ nsCOMPtr<EventTarget> targetPtr = touch->mTarget;
+ nsCOMPtr<nsIContent> content = do_QueryInterface(targetPtr);
+ if (!content) {
+ continue;
+ }
+
+ Document* doc = content->OwnerDoc();
+ nsIContent* capturingContent = PresShell::GetCapturingContent();
+ if (capturingContent) {
+ if (capturingContent->OwnerDoc() != doc) {
+ // Wrong document, don't dispatch anything.
+ continue;
+ }
+ content = capturingContent;
+ }
+ // copy the event
+ MOZ_ASSERT(touchEvent->IsTrusted());
+ WidgetTouchEvent newEvent(true, touchEvent->mMessage, touchEvent->mWidget);
+ newEvent.AssignTouchEventData(*touchEvent, false);
+ newEvent.mTarget = targetPtr;
+ newEvent.mFlags.mHandledByAPZ = touchEvent->mFlags.mHandledByAPZ;
+
+ RefPtr<PresShell> contentPresShell;
+ if (doc == GetDocument()) {
+ contentPresShell = doc->GetPresShell();
+ if (contentPresShell) {
+ // XXXsmaug huge hack. Pushing possibly capturing content,
+ // even though event target is something else.
+ contentPresShell->PushCurrentEventInfo(content->GetPrimaryFrame(),
+ content);
+ }
+ }
+
+ RefPtr<nsPresContext> presContext = doc->GetPresContext();
+ if (!presContext) {
+ if (contentPresShell) {
+ contentPresShell->PopCurrentEventInfo();
+ }
+ continue;
+ }
+
+ tmpStatus = nsEventStatus_eIgnore;
+ EventDispatcher::Dispatch(targetPtr, presContext, &newEvent, nullptr,
+ &tmpStatus, aEventCB);
+ if (nsEventStatus_eConsumeNoDefault == tmpStatus ||
+ newEvent.mFlags.mMultipleActionsPrevented) {
+ preventDefault = true;
+ }
+
+ if (newEvent.mFlags.mMultipleActionsPrevented) {
+ touchEvent->mFlags.mMultipleActionsPrevented = true;
+ }
+
+ if (contentPresShell) {
+ contentPresShell->PopCurrentEventInfo();
+ }
+ }
+
+ if (preventDefault && canPrevent) {
+ *aEventStatus = nsEventStatus_eConsumeNoDefault;
+ } else {
+ *aEventStatus = nsEventStatus_eIgnore;
+ }
+}
+
+// Dispatch event to content only (NOT full processing)
+// See also HandleEventWithTarget which does full event processing.
+nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ WidgetEvent* aEvent,
+ nsEventStatus* aStatus) {
+ nsresult rv = NS_OK;
+
+ PushCurrentEventInfo(nullptr, aTargetContent);
+
+ // Bug 41013: Check if the event should be dispatched to content.
+ // It's possible that we are in the middle of destroying the window
+ // and the js context is out of date. This check detects the case
+ // that caused a crash in bug 41013, but there may be a better way
+ // to handle this situation!
+ nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
+ if (container) {
+ // Dispatch event to content
+ rv = EventDispatcher::Dispatch(aTargetContent, mPresContext, aEvent,
+ nullptr, aStatus);
+ }
+
+ PopCurrentEventInfo();
+ return rv;
+}
+
+// See the method above.
+nsresult PresShell::HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ Event* aEvent,
+ nsEventStatus* aStatus) {
+ nsresult rv = NS_OK;
+
+ PushCurrentEventInfo(nullptr, aTargetContent);
+ nsCOMPtr<nsISupports> container = mPresContext->GetContainerWeak();
+ if (container) {
+ rv = EventDispatcher::DispatchDOMEvent(aTargetContent, nullptr, aEvent,
+ mPresContext, aStatus);
+ }
+
+ PopCurrentEventInfo();
+ return rv;
+}
+
+bool PresShell::EventHandler::AdjustContextMenuKeyEvent(
+ WidgetMouseEvent* aMouseEvent) {
+ // if a menu is open, open the context menu relative to the active item on the
+ // menu.
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ nsIFrame* popupFrame = pm->GetTopPopup(widget::PopupType::Menu);
+ if (popupFrame) {
+ nsIFrame* itemFrame = (static_cast<nsMenuPopupFrame*>(popupFrame))
+ ->GetCurrentMenuItemFrame();
+ if (!itemFrame) itemFrame = popupFrame;
+
+ nsCOMPtr<nsIWidget> widget = popupFrame->GetNearestWidget();
+ aMouseEvent->mWidget = widget;
+ LayoutDeviceIntPoint widgetPoint = widget->WidgetToScreenOffset();
+ aMouseEvent->mRefPoint =
+ LayoutDeviceIntPoint::FromAppUnitsToNearest(
+ itemFrame->GetScreenRectInAppUnits().BottomLeft(),
+ itemFrame->PresContext()->AppUnitsPerDevPixel()) -
+ widgetPoint;
+
+ mPresShell->mCurrentEventContent = itemFrame->GetContent();
+ mPresShell->mCurrentEventFrame = itemFrame;
+
+ return true;
+ }
+ }
+
+ // If we're here because of the key-equiv for showing context menus, we
+ // have to twiddle with the NS event to make sure the context menu comes
+ // up in the upper left of the relevant content area before we create
+ // the DOM event. Since we never call InitMouseEvent() on the event,
+ // the client X/Y will be 0,0. We can make use of that if the widget is null.
+ // Use the root view manager's widget since it's most likely to have one,
+ // and the coordinates returned by GetCurrentItemAndPositionForElement
+ // are relative to the widget of the root of the root view manager.
+ nsRootPresContext* rootPC = GetPresContext()->GetRootPresContext();
+ aMouseEvent->mRefPoint = LayoutDeviceIntPoint(0, 0);
+ if (rootPC) {
+ aMouseEvent->mWidget =
+ rootPC->PresShell()->GetViewManager()->GetRootWidget();
+ if (aMouseEvent->mWidget) {
+ // default the refpoint to the topleft of our document
+ nsPoint offset(0, 0);
+ nsIFrame* rootFrame = FrameConstructor()->GetRootFrame();
+ if (rootFrame) {
+ nsView* view = rootFrame->GetClosestView(&offset);
+ offset += view->GetOffsetToWidget(aMouseEvent->mWidget);
+ aMouseEvent->mRefPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
+ offset, GetPresContext()->AppUnitsPerDevPixel());
+ }
+ }
+ } else {
+ aMouseEvent->mWidget = nullptr;
+ }
+
+ // see if we should use the caret position for the popup
+ LayoutDeviceIntPoint caretPoint;
+ // Beware! This may flush notifications via synchronous
+ // ScrollSelectionIntoView.
+ if (PrepareToUseCaretPosition(MOZ_KnownLive(aMouseEvent->mWidget),
+ caretPoint)) {
+ // caret position is good
+ int32_t devPixelRatio = GetPresContext()->AppUnitsPerDevPixel();
+ caretPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(
+ ViewportUtils::LayoutToVisual(
+ LayoutDeviceIntPoint::ToAppUnits(caretPoint, devPixelRatio),
+ GetPresContext()->PresShell()),
+ devPixelRatio);
+ aMouseEvent->mRefPoint = caretPoint;
+ return true;
+ }
+
+ // If we're here because of the key-equiv for showing context menus, we
+ // have to reset the event target to the currently focused element. Get it
+ // from the focus controller.
+ RefPtr<Element> currentFocus;
+ nsFocusManager* fm = nsFocusManager::GetFocusManager();
+ if (fm) {
+ currentFocus = fm->GetFocusedElement();
+ }
+
+ // Reset event coordinates relative to focused frame in view
+ if (currentFocus) {
+ nsCOMPtr<nsIContent> currentPointElement;
+ GetCurrentItemAndPositionForElement(
+ currentFocus, getter_AddRefs(currentPointElement),
+ aMouseEvent->mRefPoint, MOZ_KnownLive(aMouseEvent->mWidget));
+ if (currentPointElement) {
+ mPresShell->mCurrentEventContent = currentPointElement;
+ mPresShell->mCurrentEventFrame = nullptr;
+ mPresShell->GetCurrentEventFrame();
+ }
+ }
+
+ return true;
+}
+
+// PresShell::EventHandler::PrepareToUseCaretPosition
+//
+// This checks to see if we should use the caret position for popup context
+// menus. Returns true if the caret position should be used, and the
+// coordinates of that position is returned in aTargetPt. This function
+// will also scroll the window as needed to make the caret visible.
+//
+// The event widget should be the widget that generated the event, and
+// whose coordinate system the resulting event's mRefPoint should be
+// relative to. The returned point is in device pixels realtive to the
+// widget passed in.
+bool PresShell::EventHandler::PrepareToUseCaretPosition(
+ nsIWidget* aEventWidget, LayoutDeviceIntPoint& aTargetPt) {
+ nsresult rv;
+
+ // check caret visibility
+ RefPtr<nsCaret> caret = mPresShell->GetCaret();
+ NS_ENSURE_TRUE(caret, false);
+
+ bool caretVisible = caret->IsVisible();
+ if (!caretVisible) return false;
+
+ // caret selection, this is a temporary weak reference, so no refcounting is
+ // needed
+ Selection* domSelection = caret->GetSelection();
+ NS_ENSURE_TRUE(domSelection, false);
+
+ // since the match could be an anonymous textnode inside a
+ // <textarea> or text <input>, we need to get the outer frame
+ // note: frames are not refcounted
+ nsIFrame* frame = nullptr; // may be nullptr
+ nsINode* node = domSelection->GetFocusNode();
+ NS_ENSURE_TRUE(node, false);
+ nsCOMPtr<nsIContent> content = nsIContent::FromNode(node);
+ if (content) {
+ nsIContent* nonNative = content->FindFirstNonChromeOnlyAccessContent();
+ content = nonNative;
+ }
+
+ if (content) {
+ // It seems like ScrollSelectionIntoView should be enough, but it's
+ // not. The problem is that scrolling the selection into view when it is
+ // below the current viewport will align the top line of the frame exactly
+ // with the bottom of the window. This is fine, BUT, the popup event causes
+ // the control to be re-focused which does this exact call to
+ // ScrollContentIntoView, which has a one-pixel disagreement of whether the
+ // frame is actually in view. The result is that the frame is aligned with
+ // the top of the window, but the menu is still at the bottom.
+ //
+ // Doing this call first forces the frame to be in view, eliminating the
+ // problem. The only difference in the result is that if your cursor is in
+ // an edit box below the current view, you'll get the edit box aligned with
+ // the top of the window. This is arguably better behavior anyway.
+ rv = MOZ_KnownLive(mPresShell)
+ ->ScrollContentIntoView(
+ content,
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollAxis(WhereToScroll::Nearest, WhenToScroll::IfNotVisible),
+ ScrollFlags::ScrollOverflowHidden);
+ NS_ENSURE_SUCCESS(rv, false);
+ frame = content->GetPrimaryFrame();
+ NS_WARNING_ASSERTION(frame, "No frame for focused content?");
+ }
+
+ // Actually scroll the selection (ie caret) into view. Note that this must
+ // be synchronous since we will be checking the caret position on the screen.
+ //
+ // Be easy about errors, and just don't scroll in those cases. Better to have
+ // the correct menu at a weird place than the wrong menu.
+ // After ScrollSelectionIntoView(), the pending notifications might be
+ // flushed and PresShell/PresContext/Frames may be dead. See bug 418470.
+ nsCOMPtr<nsISelectionController> selCon;
+ if (frame)
+ frame->GetSelectionController(GetPresContext(), getter_AddRefs(selCon));
+ else
+ selCon = static_cast<nsISelectionController*>(mPresShell);
+ if (selCon) {
+ rv = selCon->ScrollSelectionIntoView(
+ nsISelectionController::SELECTION_NORMAL,
+ nsISelectionController::SELECTION_FOCUS_REGION,
+ nsISelectionController::SCROLL_SYNCHRONOUS);
+ NS_ENSURE_SUCCESS(rv, false);
+ }
+
+ nsPresContext* presContext = GetPresContext();
+
+ // get caret position relative to the closest view
+ nsRect caretCoords;
+ nsIFrame* caretFrame = caret->GetGeometry(&caretCoords);
+ if (!caretFrame) return false;
+ nsPoint viewOffset;
+ nsView* view = caretFrame->GetClosestView(&viewOffset);
+ if (!view) return false;
+ // and then get the caret coords relative to the event widget
+ if (aEventWidget) {
+ viewOffset += view->GetOffsetToWidget(aEventWidget);
+ }
+ caretCoords.MoveBy(viewOffset);
+
+ // caret coordinates are in app units, convert to pixels
+ aTargetPt.x =
+ presContext->AppUnitsToDevPixels(caretCoords.x + caretCoords.width);
+ aTargetPt.y =
+ presContext->AppUnitsToDevPixels(caretCoords.y + caretCoords.height);
+
+ // make sure rounding doesn't return a pixel which is outside the caret
+ // (e.g. one line lower)
+ aTargetPt.y -= 1;
+
+ return true;
+}
+
+void PresShell::EventHandler::GetCurrentItemAndPositionForElement(
+ Element* aFocusedElement, nsIContent** aTargetToUse,
+ LayoutDeviceIntPoint& aTargetPt, nsIWidget* aRootWidget) {
+ nsCOMPtr<nsIContent> focusedContent = aFocusedElement;
+ MOZ_KnownLive(mPresShell)
+ ->ScrollContentIntoView(focusedContent, ScrollAxis(), ScrollAxis(),
+ ScrollFlags::ScrollOverflowHidden);
+
+ nsPresContext* presContext = GetPresContext();
+
+ bool istree = false, checkLineHeight = true;
+ nscoord extraTreeY = 0;
+
+ // Set the position to just underneath the current item for multi-select
+ // lists or just underneath the selected item for single-select lists. If
+ // the element is not a list, or there is no selection, leave the position
+ // as is.
+ nsCOMPtr<Element> item;
+ nsCOMPtr<nsIDOMXULMultiSelectControlElement> multiSelect =
+ aFocusedElement->AsXULMultiSelectControl();
+ if (multiSelect) {
+ checkLineHeight = false;
+
+ int32_t currentIndex;
+ multiSelect->GetCurrentIndex(&currentIndex);
+ if (currentIndex >= 0) {
+ RefPtr<XULTreeElement> tree = XULTreeElement::FromNode(focusedContent);
+ // Tree view special case (tree items have no frames)
+ // Get the focused row and add its coordinates, which are already in
+ // pixels
+ // XXX Boris, should we create a new interface so that this doesn't
+ // need to know about trees? Something like nsINodelessChildCreator
+ // which could provide the current focus coordinates?
+ if (tree) {
+ tree->EnsureRowIsVisible(currentIndex);
+ int32_t firstVisibleRow = tree->GetFirstVisibleRow();
+ int32_t rowHeight = tree->RowHeight();
+
+ extraTreeY += nsPresContext::CSSPixelsToAppUnits(
+ (currentIndex - firstVisibleRow + 1) * rowHeight);
+ istree = true;
+
+ RefPtr<nsTreeColumns> cols = tree->GetColumns();
+ if (cols) {
+ nsTreeColumn* col = cols->GetFirstColumn();
+ if (col) {
+ RefPtr<Element> colElement = col->Element();
+ nsIFrame* frame = colElement->GetPrimaryFrame();
+ if (frame) {
+ extraTreeY += frame->GetSize().height;
+ }
+ }
+ }
+ } else {
+ multiSelect->GetCurrentItem(getter_AddRefs(item));
+ }
+ }
+ } else {
+ // don't check menulists as the selected item will be inside a popup.
+ nsCOMPtr<nsIDOMXULMenuListElement> menulist =
+ aFocusedElement->AsXULMenuList();
+ if (!menulist) {
+ nsCOMPtr<nsIDOMXULSelectControlElement> select =
+ aFocusedElement->AsXULSelectControl();
+ if (select) {
+ checkLineHeight = false;
+ select->GetSelectedItem(getter_AddRefs(item));
+ }
+ }
+ }
+
+ if (item) {
+ focusedContent = item;
+ }
+
+ nsIFrame* frame = focusedContent->GetPrimaryFrame();
+ if (frame) {
+ NS_ASSERTION(
+ frame->PresContext() == GetPresContext(),
+ "handling event for focused content that is not in our document?");
+
+ nsPoint frameOrigin(0, 0);
+
+ // Get the frame's origin within its view
+ nsView* view = frame->GetClosestView(&frameOrigin);
+ NS_ASSERTION(view, "No view for frame");
+
+ // View's origin relative the widget
+ if (aRootWidget) {
+ frameOrigin += view->GetOffsetToWidget(aRootWidget);
+ }
+
+ // Start context menu down and to the right from top left of frame
+ // use the lineheight. This is a good distance to move the context
+ // menu away from the top left corner of the frame. If we always
+ // used the frame height, the context menu could end up far away,
+ // for example when we're focused on linked images.
+ // On the other hand, we want to use the frame height if it's less
+ // than the current line height, so that the context menu appears
+ // associated with the correct frame.
+ nscoord extra = 0;
+ if (!istree) {
+ extra = frame->GetSize().height;
+ if (checkLineHeight) {
+ nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::GetNearestScrollableFrame(
+ frame, nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN |
+ nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT);
+ if (scrollFrame) {
+ nsSize scrollAmount = scrollFrame->GetLineScrollAmount();
+ nsIFrame* f = do_QueryFrame(scrollFrame);
+ int32_t APD = presContext->AppUnitsPerDevPixel();
+ int32_t scrollAPD = f->PresContext()->AppUnitsPerDevPixel();
+ scrollAmount = scrollAmount.ScaleToOtherAppUnits(scrollAPD, APD);
+ if (extra > scrollAmount.height) {
+ extra = scrollAmount.height;
+ }
+ }
+ }
+ }
+
+ aTargetPt.x = presContext->AppUnitsToDevPixels(frameOrigin.x);
+ aTargetPt.y =
+ presContext->AppUnitsToDevPixels(frameOrigin.y + extra + extraTreeY);
+ }
+
+ NS_IF_ADDREF(*aTargetToUse = focusedContent);
+}
+
+bool PresShell::ShouldIgnoreInvalidation() {
+ return mPaintingSuppressed || !mIsActive || mIsNeverPainting;
+}
+
+void PresShell::WillPaint() {
+ // Check the simplest things first. In particular, it's important to
+ // check mIsActive before making any of the more expensive calls such
+ // as GetRootPresContext, for the case of a browser with a large
+ // number of tabs.
+ // Don't bother doing anything if some viewmanager in our tree is painting
+ // while we still have painting suppressed or we are not active.
+ if (!mIsActive || mPaintingSuppressed || !IsVisible()) {
+ return;
+ }
+
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (!rootPresContext) {
+ // In some edge cases, such as when we don't have a root frame yet,
+ // we can't find the root prescontext. There's nothing to do in that
+ // case.
+ return;
+ }
+
+ rootPresContext->FlushWillPaintObservers();
+ if (mIsDestroying) return;
+
+ // Process reflows, if we have them, to reduce flicker due to invalidates and
+ // reflow being interspersed. Note that we _do_ allow this to be
+ // interruptible; if we can't do all the reflows it's better to flicker a bit
+ // than to freeze up.
+ FlushPendingNotifications(
+ ChangesToFlush(FlushType::InterruptibleLayout, false));
+}
+
+void PresShell::DidPaintWindow() {
+ nsRootPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext != mPresContext) {
+ // This could be a popup's presshell. No point in notifying XPConnect
+ // about compositing of popups.
+ return;
+ }
+
+ if (!mHasReceivedPaintMessage) {
+ mHasReceivedPaintMessage = true;
+
+ nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService();
+ if (obsvc && mDocument) {
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+ if (window && nsGlobalWindowOuter::Cast(window)->IsChromeWindow()) {
+ obsvc->NotifyObservers(window, "widget-first-paint", nullptr);
+ }
+ }
+ }
+}
+
+bool PresShell::IsVisible() const {
+ if (!mIsActive || !mViewManager) return false;
+
+ nsView* view = mViewManager->GetRootView();
+ if (!view) return true;
+
+ // inner view of subdoc frame
+ view = view->GetParent();
+ if (!view) return true;
+
+ // subdoc view
+ view = view->GetParent();
+ if (!view) return true;
+
+ nsIFrame* frame = view->GetFrame();
+ if (!frame) return true;
+
+ return frame->IsVisibleConsideringAncestors(
+ nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY);
+}
+
+void PresShell::SuppressDisplayport(bool aEnabled) {
+ if (aEnabled) {
+ mActiveSuppressDisplayport++;
+ } else if (mActiveSuppressDisplayport > 0) {
+ bool isSuppressed = IsDisplayportSuppressed();
+ mActiveSuppressDisplayport--;
+ if (isSuppressed && !IsDisplayportSuppressed()) {
+ // We unsuppressed the displayport, trigger a paint
+ if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
+ rootFrame->SchedulePaint();
+ }
+ }
+ }
+}
+
+static bool sDisplayPortSuppressionRespected = true;
+
+void PresShell::RespectDisplayportSuppression(bool aEnabled) {
+ bool isSuppressed = IsDisplayportSuppressed();
+ sDisplayPortSuppressionRespected = aEnabled;
+ if (isSuppressed && !IsDisplayportSuppressed()) {
+ // We unsuppressed the displayport, trigger a paint
+ if (nsIFrame* rootFrame = mFrameConstructor->GetRootFrame()) {
+ rootFrame->SchedulePaint();
+ }
+ }
+}
+
+bool PresShell::IsDisplayportSuppressed() {
+ return sDisplayPortSuppressionRespected && mActiveSuppressDisplayport > 0;
+}
+
+static CallState FreezeSubDocument(Document& aDocument) {
+ if (PresShell* presShell = aDocument.GetPresShell()) {
+ presShell->Freeze();
+ }
+ return CallState::Continue;
+}
+
+void PresShell::Freeze(bool aIncludeSubDocuments) {
+ mUpdateApproximateFrameVisibilityEvent.Revoke();
+
+ MaybeReleaseCapturingContent();
+
+ if (mCaret) {
+ SetCaretEnabled(false);
+ }
+
+ mPaintingSuppressed = true;
+
+ if (aIncludeSubDocuments && mDocument) {
+ mDocument->EnumerateSubDocuments(FreezeSubDocument);
+ }
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext) {
+ presContext->DisableInteractionTimeRecording();
+ if (presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->Freeze();
+ }
+
+ if (nsPresContext* rootPresContext = presContext->GetRootPresContext()) {
+ rootPresContext->ResetUserInputEventsAllowed();
+ }
+ }
+
+ mFrozen = true;
+ if (mDocument) {
+ UpdateImageLockingState();
+ }
+}
+
+void PresShell::FireOrClearDelayedEvents(bool aFireEvents) {
+ mNoDelayedMouseEvents = false;
+ mNoDelayedKeyEvents = false;
+ if (!aFireEvents) {
+ mDelayedEvents.Clear();
+ return;
+ }
+
+ if (mDocument) {
+ RefPtr<Document> doc = mDocument;
+ while (!mIsDestroying && mDelayedEvents.Length() &&
+ !doc->EventHandlingSuppressed()) {
+ UniquePtr<DelayedEvent> ev = std::move(mDelayedEvents[0]);
+ mDelayedEvents.RemoveElementAt(0);
+ if (ev->IsKeyPressEvent() && mIsLastKeyDownCanceled) {
+ continue;
+ }
+ ev->Dispatch();
+ }
+ if (!doc->EventHandlingSuppressed()) {
+ mDelayedEvents.Clear();
+ }
+ }
+}
+
+void PresShell::Thaw(bool aIncludeSubDocuments) {
+ nsPresContext* presContext = GetPresContext();
+ if (presContext &&
+ presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->Thaw();
+ }
+
+ if (aIncludeSubDocuments && mDocument) {
+ mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
+ if (PresShell* presShell = aSubDoc.GetPresShell()) {
+ presShell->Thaw();
+ }
+ return CallState::Continue;
+ });
+ }
+
+ // Get the activeness of our presshell, as this might have changed
+ // while we were in the bfcache
+ ActivenessMaybeChanged();
+
+ // We're now unfrozen
+ mFrozen = false;
+ UpdateImageLockingState();
+
+ UnsuppressPainting();
+
+ // In case the above UnsuppressPainting call didn't start the
+ // refresh driver, we manually start the refresh driver to
+ // ensure nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading
+ // can be called for user input events handling.
+ if (presContext && presContext->IsRoot()) {
+ if (!presContext->RefreshDriver()->HasPendingTick()) {
+ presContext->RefreshDriver()->InitializeTimer();
+ }
+ }
+}
+
+//--------------------------------------------------------
+// Start of protected and private methods on the PresShell
+//--------------------------------------------------------
+
+void PresShell::MaybeScheduleReflow() {
+ ASSERT_REFLOW_SCHEDULED_STATE();
+ if (mObservingLayoutFlushes || mIsDestroying || mIsReflowing ||
+ mDirtyRoots.IsEmpty())
+ return;
+
+ if (!mPresContext->HasPendingInterrupt() || !ScheduleReflowOffTimer()) {
+ ScheduleReflow();
+ }
+
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+void PresShell::ScheduleReflow() {
+ ASSERT_REFLOW_SCHEDULED_STATE();
+ DoObserveLayoutFlushes();
+ ASSERT_REFLOW_SCHEDULED_STATE();
+}
+
+void PresShell::WillCauseReflow() {
+ nsContentUtils::AddScriptBlocker();
+ ++mChangeNestCount;
+}
+
+void PresShell::DidCauseReflow() {
+ NS_ASSERTION(mChangeNestCount != 0, "Unexpected call to DidCauseReflow()");
+ --mChangeNestCount;
+ nsContentUtils::RemoveScriptBlocker();
+}
+
+void PresShell::WillDoReflow() {
+ mDocument->FlushUserFontSet();
+
+ mPresContext->FlushCounterStyles();
+
+ mPresContext->FlushFontFeatureValues();
+
+ mPresContext->FlushFontPaletteValues();
+
+ mLastReflowStart = GetPerformanceNowUnclamped();
+}
+
+void PresShell::DidDoReflow(bool aInterruptible) {
+ MOZ_ASSERT(mPendingDidDoReflow);
+ if (!nsContentUtils::IsSafeToRunScript()) {
+ // If we're reflowing while script-blocked (e.g. from container query
+ // updates), defer our reflow callbacks until the end of our next layout
+ // flush.
+ SetNeedLayoutFlush();
+ return;
+ }
+
+ auto clearPendingDidDoReflow =
+ MakeScopeExit([&] { mPendingDidDoReflow = false; });
+
+ mHiddenContentInForcedLayout.Clear();
+
+ HandlePostedReflowCallbacks(aInterruptible);
+
+ if (mIsDestroying) {
+ return;
+ }
+
+ {
+ nsAutoScriptBlocker scriptBlocker;
+ AutoAssertNoFlush noReentrantFlush(*this);
+ if (nsCOMPtr<nsIDocShell> docShell = mPresContext->GetDocShell()) {
+ DOMHighResTimeStamp now = GetPerformanceNowUnclamped();
+ docShell->NotifyReflowObservers(aInterruptible, mLastReflowStart, now);
+ }
+
+ if (StaticPrefs::layout_reflow_synthMouseMove()) {
+ SynthesizeMouseMove(false);
+ }
+
+ mPresContext->NotifyMissingFonts();
+ }
+
+ if (mIsDestroying) {
+ return;
+ }
+
+ if (mDirtyRoots.IsEmpty()) {
+ // We only unsuppress painting if we're out of reflows. It's pointless to
+ // do so if reflows are still pending, since reflows are just going to
+ // thrash the frames around some more. By waiting we avoid an overeager
+ // "jitter" effect.
+ if (mShouldUnsuppressPainting) {
+ mShouldUnsuppressPainting = false;
+ UnsuppressAndInvalidate();
+ }
+ } else {
+ // If any new reflow commands were enqueued during the reflow, schedule
+ // another reflow event to process them. Note that we want to do this
+ // after DidDoReflow(), since that method can change whether there are
+ // dirty roots around by flushing, and there's no point in posting a
+ // reflow event just to have the flush revoke it.
+ MaybeScheduleReflow();
+ // And record that we might need flushing
+ SetNeedLayoutFlush();
+ }
+}
+
+DOMHighResTimeStamp PresShell::GetPerformanceNowUnclamped() {
+ DOMHighResTimeStamp now = 0;
+
+ if (nsPIDOMWindowInner* window = mDocument->GetInnerWindow()) {
+ Performance* perf = window->GetPerformance();
+
+ if (perf) {
+ now = perf->NowUnclamped();
+ }
+ }
+
+ return now;
+}
+
+void PresShell::sReflowContinueCallback(nsITimer* aTimer, void* aPresShell) {
+ RefPtr<PresShell> self = static_cast<PresShell*>(aPresShell);
+
+ MOZ_ASSERT(aTimer == self->mReflowContinueTimer, "Unexpected timer");
+ self->mReflowContinueTimer = nullptr;
+ self->ScheduleReflow();
+}
+
+bool PresShell::ScheduleReflowOffTimer() {
+ MOZ_ASSERT(!mObservingLayoutFlushes, "Shouldn't get here");
+ ASSERT_REFLOW_SCHEDULED_STATE();
+
+ if (!mReflowContinueTimer) {
+ nsresult rv = NS_NewTimerWithFuncCallback(
+ getter_AddRefs(mReflowContinueTimer), sReflowContinueCallback, this, 30,
+ nsITimer::TYPE_ONE_SHOT, "sReflowContinueCallback",
+ GetMainThreadSerialEventTarget());
+ return NS_SUCCEEDED(rv);
+ }
+ return true;
+}
+
+bool PresShell::DoReflow(nsIFrame* target, bool aInterruptible,
+ OverflowChangedTracker* aOverflowTracker) {
+ [[maybe_unused]] nsIURI* uri = mDocument->GetDocumentURI();
+ AUTO_PROFILER_LABEL_DYNAMIC_NSCSTRING_RELEVANT_FOR_JS(
+ "Reflow", LAYOUT_Reflow, uri ? uri->GetSpecOrDefault() : "N/A"_ns);
+
+ LAYOUT_TELEMETRY_RECORD_BASE(Reflow);
+
+ PerfStats::AutoMetricRecording<PerfStats::Metric::Reflowing> autoRecording;
+
+ gfxTextPerfMetrics* tp = mPresContext->GetTextPerfMetrics();
+ TimeStamp timeStart;
+ if (tp) {
+ tp->Accumulate();
+ tp->reflowCount++;
+ timeStart = TimeStamp::Now();
+ }
+
+ // Schedule a paint, but don't actually mark this frame as changed for
+ // retained DL building purposes. If any child frames get moved, then
+ // they will schedule paint again. We could probaby skip this, and just
+ // schedule a similar paint when a frame is deleted.
+ target->SchedulePaint(nsIFrame::PAINT_DEFAULT, false);
+
+ Maybe<uint64_t> innerWindowID;
+ if (auto* window = mDocument->GetInnerWindow()) {
+ innerWindowID = Some(window->WindowID());
+ }
+ AutoProfilerTracing tracingLayoutFlush(
+ "Paint", aInterruptible ? "Reflow (interruptible)" : "Reflow (sync)",
+ geckoprofiler::category::LAYOUT, std::move(mReflowCause), innerWindowID);
+ mReflowCause = nullptr;
+
+ FlushPendingScrollAnchorSelections();
+
+ if (mReflowContinueTimer) {
+ mReflowContinueTimer->Cancel();
+ mReflowContinueTimer = nullptr;
+ }
+
+ const bool isRoot = target == mFrameConstructor->GetRootFrame();
+
+ MOZ_ASSERT(isRoot || aOverflowTracker,
+ "caller must provide overflow tracker when reflowing "
+ "non-root frames");
+
+ // CreateReferenceRenderingContext can return nullptr
+ UniquePtr<gfxContext> rcx(CreateReferenceRenderingContext());
+
+#ifdef DEBUG
+ mCurrentReflowRoot = target;
+#endif
+
+ // If the target frame is the root of the frame hierarchy, then
+ // use all the available space. If it's simply a `reflow root',
+ // then use the target frame's size as the available space.
+ WritingMode wm = target->GetWritingMode();
+ LogicalSize size(wm);
+ if (isRoot) {
+ size = LogicalSize(wm, mPresContext->GetVisibleArea().Size());
+ } else {
+ size = target->GetLogicalSize();
+ }
+
+ OverflowAreas oldOverflow; // initialized and used only when !isRoot
+ if (!isRoot) {
+ oldOverflow = target->GetOverflowAreas();
+ }
+
+ NS_ASSERTION(!target->GetNextInFlow() && !target->GetPrevInFlow(),
+ "reflow roots should never split");
+
+ // Don't pass size directly to the reflow input, since a
+ // constrained height implies page/column breaking.
+ LogicalSize reflowSize(wm, size.ISize(wm), NS_UNCONSTRAINEDSIZE);
+ ReflowInput reflowInput(mPresContext, target, rcx.get(), reflowSize,
+ ReflowInput::InitFlag::CallerWillInit);
+ reflowInput.mOrthogonalLimit = size.BSize(wm);
+
+ if (isRoot) {
+ reflowInput.Init(mPresContext);
+
+ // When the root frame is being reflowed with unconstrained block-size
+ // (which happens when we're called from
+ // nsDocumentViewer::SizeToContent), we're effectively doing a
+ // resize in the block direction, since it changes the meaning of
+ // percentage block-sizes even if no block-sizes actually changed.
+ // The same applies when we reflow again after that computation. This is
+ // an unusual case, and isn't caught by ReflowInput::InitResizeFlags.
+ bool hasUnconstrainedBSize = size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
+
+ if (hasUnconstrainedBSize || mLastRootReflowHadUnconstrainedBSize) {
+ reflowInput.SetBResize(true);
+ }
+
+ mLastRootReflowHadUnconstrainedBSize = hasUnconstrainedBSize;
+ } else {
+ // Initialize reflow input with current used border and padding,
+ // in case this was set specially by the parent frame when the reflow root
+ // was reflowed by its parent.
+ reflowInput.Init(mPresContext, Nothing(),
+ Some(target->GetLogicalUsedBorder(wm)),
+ Some(target->GetLogicalUsedPadding(wm)));
+ }
+
+ // fix the computed height
+ NS_ASSERTION(reflowInput.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0),
+ "reflow input should not set margin for reflow roots");
+ if (size.BSize(wm) != NS_UNCONSTRAINEDSIZE) {
+ nscoord computedBSize =
+ size.BSize(wm) -
+ reflowInput.ComputedLogicalBorderPadding(wm).BStartEnd(wm);
+ computedBSize = std::max(computedBSize, 0);
+ reflowInput.SetComputedBSize(computedBSize);
+ }
+ NS_ASSERTION(
+ reflowInput.ComputedISize() ==
+ size.ISize(wm) -
+ reflowInput.ComputedLogicalBorderPadding(wm).IStartEnd(wm),
+ "reflow input computed incorrect inline size");
+
+ mPresContext->ReflowStarted(aInterruptible);
+ mIsReflowing = true;
+
+ nsReflowStatus status;
+ ReflowOutput desiredSize(reflowInput);
+ target->Reflow(mPresContext, desiredSize, reflowInput, status);
+
+ // If an incremental reflow is initiated at a frame other than the
+ // root frame, then its desired size had better not change! If it's
+ // initiated at the root, then the size better not change unless its
+ // height was unconstrained to start with.
+ nsRect boundsRelativeToTarget =
+ nsRect(0, 0, desiredSize.Width(), desiredSize.Height());
+ const bool isBSizeLimitReflow =
+ isRoot && size.BSize(wm) == NS_UNCONSTRAINEDSIZE;
+ NS_ASSERTION(isBSizeLimitReflow || desiredSize.Size(wm) == size,
+ "non-root frame's desired size changed during an "
+ "incremental reflow");
+ NS_ASSERTION(status.IsEmpty(), "reflow roots should never split");
+
+ target->SetSize(boundsRelativeToTarget.Size());
+
+ // Always use boundsRelativeToTarget here, not desiredSize.InkOverflowRect(),
+ // because for root frames (where they could be different, since root frames
+ // are allowed to have overflow) the root view bounds need to match the
+ // viewport bounds; the view manager "window dimensions" code depends on it.
+ if (target->HasView()) {
+ nsContainerFrame::SyncFrameViewAfterReflow(
+ mPresContext, target, target->GetView(), boundsRelativeToTarget);
+ if (target->IsViewportFrame()) {
+ SyncWindowProperties(/* aSync = */ false);
+ }
+ }
+
+ target->DidReflow(mPresContext, nullptr);
+ if (target->IsInScrollAnchorChain()) {
+ ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(target);
+ PostPendingScrollAnchorAdjustment(container);
+ }
+ if (MOZ_UNLIKELY(isBSizeLimitReflow)) {
+ mPresContext->SetVisibleArea(boundsRelativeToTarget);
+ }
+
+#ifdef DEBUG
+ mCurrentReflowRoot = nullptr;
+#endif
+
+ if (!isRoot && oldOverflow != target->GetOverflowAreas()) {
+ // The overflow area changed. Propagate this change to ancestors.
+ aOverflowTracker->AddFrame(target->GetParent(),
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+
+ NS_ASSERTION(
+ mPresContext->HasPendingInterrupt() || mFramesToDirty.Count() == 0,
+ "Why do we need to dirty anything if not interrupted?");
+
+ mIsReflowing = false;
+ bool interrupted = mPresContext->HasPendingInterrupt();
+ if (interrupted) {
+ // Make sure target gets reflowed again.
+ for (const auto& key : mFramesToDirty) {
+ // Mark frames dirty until target frame.
+ for (nsIFrame* f = key; f && !f->IsSubtreeDirty(); f = f->GetParent()) {
+ f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN);
+ if (f->IsFlexItem()) {
+ nsFlexContainerFrame::MarkCachedFlexMeasurementsDirty(f);
+ }
+
+ if (f == target) {
+ break;
+ }
+ }
+ }
+
+ NS_ASSERTION(target->IsSubtreeDirty(), "Why is the target not dirty?");
+ mDirtyRoots.Add(target);
+ SetNeedLayoutFlush();
+
+ // Clear mFramesToDirty after we've done the target->IsSubtreeDirty()
+ // assertion so that if it fails it's easier to see what's going on.
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ printf("mFramesToDirty.Count() == %u\n", mFramesToDirty.Count());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+ mFramesToDirty.Clear();
+
+ // Any FlushPendingNotifications with interruptible reflows
+ // should be suppressed now. We don't want to do extra reflow work
+ // before our reflow event happens.
+ mWasLastReflowInterrupted = true;
+ MaybeScheduleReflow();
+ }
+
+ // dump text perf metrics for reflows with significant text processing
+ if (tp) {
+ if (tp->current.numChars > 100) {
+ TimeDuration reflowTime = TimeStamp::Now() - timeStart;
+ LogTextPerfStats(tp, this, tp->current, reflowTime.ToMilliseconds(),
+ eLog_reflow, nullptr);
+ }
+ tp->Accumulate();
+ }
+
+ return !interrupted;
+}
+
+#ifdef DEBUG
+void PresShell::DoVerifyReflow() {
+ if (GetVerifyReflowEnable()) {
+ // First synchronously render what we have so far so that we can
+ // see it.
+ nsView* rootView = mViewManager->GetRootView();
+ mViewManager->InvalidateView(rootView);
+
+ FlushPendingNotifications(FlushType::Layout);
+ mInVerifyReflow = true;
+ bool ok = VerifyIncrementalReflow();
+ mInVerifyReflow = false;
+ if (VerifyReflowFlags::All & gVerifyReflowFlags) {
+ printf("ProcessReflowCommands: finished (%s)\n", ok ? "ok" : "failed");
+ }
+
+ if (!mDirtyRoots.IsEmpty()) {
+ printf("XXX yikes! reflow commands queued during verify-reflow\n");
+ }
+ }
+}
+#endif
+
+// used with Telemetry metrics
+#define NS_LONG_REFLOW_TIME_MS 5000
+
+bool PresShell::ProcessReflowCommands(bool aInterruptible) {
+ if (mDirtyRoots.IsEmpty() && !mShouldUnsuppressPainting &&
+ !mPendingDidDoReflow) {
+ // Nothing to do; bail out
+ return true;
+ }
+
+ const bool wasProcessingReflowCommands = mProcessingReflowCommands;
+ auto restoreProcessingReflowCommands = MakeScopeExit(
+ [&] { mProcessingReflowCommands = wasProcessingReflowCommands; });
+ mProcessingReflowCommands = true;
+
+ auto timerStart = mozilla::TimeStamp::Now();
+ bool interrupted = false;
+ if (!mDirtyRoots.IsEmpty()) {
+#ifdef DEBUG
+ if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
+ printf("ProcessReflowCommands: begin incremental reflow\n");
+ }
+#endif
+
+ // If reflow is interruptible, then make a note of our deadline.
+ const PRIntervalTime deadline =
+ aInterruptible
+ ? PR_IntervalNow() + PR_MicrosecondsToInterval(gMaxRCProcessingTime)
+ : (PRIntervalTime)0;
+
+ // Scope for the reflow entry point
+ nsAutoScriptBlocker scriptBlocker;
+ WillDoReflow();
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(GetPresContext(), Reflow);
+ nsViewManager::AutoDisableRefresh refreshBlocker(mViewManager);
+
+ OverflowChangedTracker overflowTracker;
+
+ do {
+ // Send an incremental reflow notification to the target frame.
+ nsIFrame* target = mDirtyRoots.PopShallowestRoot();
+
+ if (!target->IsSubtreeDirty()) {
+ // It's not dirty anymore, which probably means the notification
+ // was posted in the middle of a reflow (perhaps with a reflow
+ // root in the middle). Don't do anything.
+ continue;
+ }
+
+ interrupted = !DoReflow(target, aInterruptible, &overflowTracker);
+
+ // Keep going until we're out of reflow commands, or we've run
+ // past our deadline, or we're interrupted.
+ } while (!interrupted && !mDirtyRoots.IsEmpty() &&
+ (!aInterruptible || PR_IntervalNow() < deadline));
+
+ interrupted = !mDirtyRoots.IsEmpty();
+
+ overflowTracker.Flush();
+
+ if (!interrupted) {
+ // We didn't get interrupted. Go ahead and perform scroll anchor
+ // adjustments.
+ FlushPendingScrollAnchorAdjustments();
+ }
+ mPendingDidDoReflow = true;
+ }
+
+ // Exiting the scriptblocker might have killed us. If we were processing
+ // scroll commands, let the outermost call deal with it.
+ if (!mIsDestroying && mPendingDidDoReflow && !wasProcessingReflowCommands) {
+ DidDoReflow(aInterruptible);
+ }
+
+#ifdef DEBUG
+ if (VerifyReflowFlags::DumpCommands & gVerifyReflowFlags) {
+ printf("\nPresShell::ProcessReflowCommands() finished: this=%p\n",
+ (void*)this);
+ }
+ DoVerifyReflow();
+#endif
+
+ {
+ TimeDuration elapsed = TimeStamp::Now() - timerStart;
+ int32_t intElapsed = int32_t(elapsed.ToMilliseconds());
+ if (intElapsed > NS_LONG_REFLOW_TIME_MS) {
+ Telemetry::Accumulate(Telemetry::LONG_REFLOW_INTERRUPTIBLE,
+ aInterruptible ? 1 : 0);
+ }
+ }
+
+ return !interrupted;
+}
+
+bool PresShell::DoFlushLayout(bool aInterruptible) {
+ mFrameConstructor->RecalcQuotesAndCounters();
+ return ProcessReflowCommands(aInterruptible);
+}
+
+void PresShell::WindowSizeMoveDone() {
+ if (mPresContext) {
+ EventStateManager::ClearGlobalActiveContent(nullptr);
+ ClearMouseCapture();
+ }
+}
+
+NS_IMETHODIMP
+PresShell::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (mIsDestroying) {
+ NS_WARNING("our observers should have been unregistered by now");
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "memory-pressure")) {
+ if (!AssumeAllFramesVisible() &&
+ mPresContext->IsRootContentDocumentInProcess()) {
+ DoUpdateApproximateFrameVisibility(/* aRemoveOnly = */ true);
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, NS_WIDGET_WAKE_OBSERVER_TOPIC)) {
+ mLastOSWake = TimeStamp::Now();
+ return NS_OK;
+ }
+
+ // For parent process, user may expect the UI is interactable after a
+ // tab (previously opened page or home page) has restored.
+ if (!nsCRT::strcmp(aTopic, "sessionstore-one-or-no-tab-restored")) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ sProcessInteractable = true;
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->RemoveObserver(this, "sessionstore-one-or-no-tab-restored");
+ }
+ return NS_OK;
+ }
+
+ if (!nsCRT::strcmp(aTopic, "font-info-updated")) {
+ // See how gfxPlatform::ForceGlobalReflow encodes this.
+ bool needsReframe = aData && !!aData[0];
+ mPresContext->ForceReflowForFontInfoUpdate(needsReframe);
+ return NS_OK;
+ }
+
+ // The "look-and-feel-changed" notification for JS observers will be
+ // dispatched HandleGlobalThemeChange once LookAndFeel caches are cleared.
+ if (!nsCRT::strcmp(aTopic, "internal-look-and-feel-changed")) {
+ // See how LookAndFeel::NotifyChangedAllWindows encodes this.
+ auto kind = widget::ThemeChangeKind(aData[0]);
+ mPresContext->ThemeChanged(kind);
+ return NS_OK;
+ }
+
+ NS_WARNING("unrecognized topic in PresShell::Observe");
+ return NS_ERROR_FAILURE;
+}
+
+bool PresShell::AddRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType,
+ const char* aObserverDescription) {
+ nsPresContext* presContext = GetPresContext();
+ if (MOZ_UNLIKELY(!presContext)) {
+ return false;
+ }
+ presContext->RefreshDriver()->AddRefreshObserver(aObserver, aFlushType,
+ aObserverDescription);
+ return true;
+}
+
+bool PresShell::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType) {
+ nsPresContext* presContext = GetPresContext();
+ return presContext && presContext->RefreshDriver()->RemoveRefreshObserver(
+ aObserver, aFlushType);
+}
+
+bool PresShell::AddPostRefreshObserver(nsAPostRefreshObserver* aObserver) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return false;
+ }
+ presContext->RefreshDriver()->AddPostRefreshObserver(aObserver);
+ return true;
+}
+
+bool PresShell::RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver) {
+ nsPresContext* presContext = GetPresContext();
+ if (!presContext) {
+ return false;
+ }
+ presContext->RefreshDriver()->RemovePostRefreshObserver(aObserver);
+ return true;
+}
+
+void PresShell::DoObserveStyleFlushes() {
+ MOZ_ASSERT(!ObservingStyleFlushes());
+ mObservingStyleFlushes = true;
+
+ if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
+ mPresContext->RefreshDriver()->AddStyleFlushObserver(this);
+ }
+}
+
+void PresShell::DoObserveLayoutFlushes() {
+ MOZ_ASSERT(!ObservingLayoutFlushes());
+ mObservingLayoutFlushes = true;
+
+ if (MOZ_LIKELY(!mDocument->GetBFCacheEntry())) {
+ mPresContext->RefreshDriver()->AddLayoutFlushObserver(this);
+ }
+}
+
+//------------------------------------------------------
+// End of protected and private methods on the PresShell
+//------------------------------------------------------
+
+//------------------------------------------------------------------
+//-- Delayed event Classes Impls
+//------------------------------------------------------------------
+
+PresShell::DelayedInputEvent::DelayedInputEvent()
+ : DelayedEvent(), mEvent(nullptr) {}
+
+PresShell::DelayedInputEvent::~DelayedInputEvent() { delete mEvent; }
+
+void PresShell::DelayedInputEvent::Dispatch() {
+ if (!mEvent || !mEvent->mWidget) {
+ return;
+ }
+ nsCOMPtr<nsIWidget> widget = mEvent->mWidget;
+ nsEventStatus status;
+ widget->DispatchEvent(mEvent, status);
+}
+
+PresShell::DelayedMouseEvent::DelayedMouseEvent(WidgetMouseEvent* aEvent) {
+ MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
+ WidgetMouseEvent* mouseEvent =
+ new WidgetMouseEvent(true, aEvent->mMessage, aEvent->mWidget,
+ aEvent->mReason, aEvent->mContextMenuTrigger);
+ mouseEvent->AssignMouseEventData(*aEvent, false);
+ mEvent = mouseEvent;
+}
+
+PresShell::DelayedKeyEvent::DelayedKeyEvent(WidgetKeyboardEvent* aEvent) {
+ MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
+ WidgetKeyboardEvent* keyEvent =
+ new WidgetKeyboardEvent(true, aEvent->mMessage, aEvent->mWidget);
+ keyEvent->AssignKeyEventData(*aEvent, false);
+ keyEvent->mFlags.mIsSynthesizedForTests =
+ aEvent->mFlags.mIsSynthesizedForTests;
+ keyEvent->mFlags.mIsSuppressedOrDelayed = true;
+ mEvent = keyEvent;
+}
+
+bool PresShell::DelayedKeyEvent::IsKeyPressEvent() {
+ return mEvent->mMessage == eKeyPress;
+}
+
+// Start of DEBUG only code
+
+#ifdef DEBUG
+
+static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg) {
+ nsAutoString n1, n2;
+ if (k1) {
+ k1->GetFrameName(n1);
+ } else {
+ n1.AssignLiteral(u"(null)");
+ }
+
+ if (k2) {
+ k2->GetFrameName(n2);
+ } else {
+ n2.AssignLiteral(u"(null)");
+ }
+
+ printf("verifyreflow: %s %p != %s %p %s\n",
+ NS_LossyConvertUTF16toASCII(n1).get(), (void*)k1,
+ NS_LossyConvertUTF16toASCII(n2).get(), (void*)k2, aMsg);
+}
+
+static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
+ const nsRect& r1, const nsRect& r2) {
+ printf("VerifyReflow Error:\n");
+ nsAutoString name;
+
+ if (k1) {
+ k1->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
+ }
+ printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
+
+ if (k2) {
+ k2->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
+ }
+ printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
+}
+
+static void LogVerifyMessage(nsIFrame* k1, nsIFrame* k2, const char* aMsg,
+ const nsIntRect& r1, const nsIntRect& r2) {
+ printf("VerifyReflow Error:\n");
+ nsAutoString name;
+
+ if (k1) {
+ k1->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k1);
+ }
+ printf("{%d, %d, %d, %d} != \n", r1.x, r1.y, r1.width, r1.height);
+
+ if (k2) {
+ k2->GetFrameName(name);
+ printf(" %s %p ", NS_LossyConvertUTF16toASCII(name).get(), (void*)k2);
+ }
+ printf("{%d, %d, %d, %d}\n %s\n", r2.x, r2.y, r2.width, r2.height, aMsg);
+}
+
+static bool CompareTrees(nsPresContext* aFirstPresContext,
+ nsIFrame* aFirstFrame,
+ nsPresContext* aSecondPresContext,
+ nsIFrame* aSecondFrame) {
+ if (!aFirstPresContext || !aFirstFrame || !aSecondPresContext ||
+ !aSecondFrame)
+ return true;
+ // XXX Evil hack to reduce false positives; I can't seem to figure
+ // out how to flush scrollbar changes correctly
+ // if (aFirstFrame->IsScrollbarFrame())
+ // return true;
+ bool ok = true;
+ const auto& childLists1 = aFirstFrame->ChildLists();
+ const auto& childLists2 = aSecondFrame->ChildLists();
+ auto iterLists1 = childLists1.begin();
+ auto iterLists2 = childLists2.begin();
+ do {
+ const nsFrameList& kids1 = iterLists1 != childLists1.end()
+ ? iterLists1->mList
+ : nsFrameList::EmptyList();
+ const nsFrameList& kids2 = iterLists2 != childLists2.end()
+ ? iterLists2->mList
+ : nsFrameList::EmptyList();
+ int32_t l1 = kids1.GetLength();
+ int32_t l2 = kids2.GetLength();
+ if (l1 != l2) {
+ ok = false;
+ LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
+ "child counts don't match: ");
+ printf("%d != %d\n", l1, l2);
+ if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
+ break;
+ }
+ }
+
+ LayoutDeviceIntRect r1, r2;
+ nsView* v1;
+ nsView* v2;
+ for (auto kids1Iter = kids1.begin(), kids2Iter = kids2.begin();;
+ ++kids1Iter, ++kids2Iter) {
+ nsIFrame* k1 = *kids1Iter;
+ nsIFrame* k2 = *kids2Iter;
+ if (((nullptr == k1) && (nullptr != k2)) ||
+ ((nullptr != k1) && (nullptr == k2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child lists are different\n");
+ break;
+ } else if (nullptr != k1) {
+ // Verify that the frames are the same size
+ if (!k1->GetRect().IsEqualInterior(k2->GetRect())) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "(frame rects)", k1->GetRect(),
+ k2->GetRect());
+ }
+
+ // Make sure either both have views or neither have views; if they
+ // do have views, make sure the views are the same size. If the
+ // views have widgets, make sure they both do or neither does. If
+ // they do, make sure the widgets are the same size.
+ v1 = k1->GetView();
+ v2 = k2->GetView();
+ if (((nullptr == v1) && (nullptr != v2)) ||
+ ((nullptr != v1) && (nullptr == v2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child views are not matched\n");
+ } else if (nullptr != v1) {
+ if (!v1->GetBounds().IsEqualInterior(v2->GetBounds())) {
+ LogVerifyMessage(k1, k2, "(view rects)", v1->GetBounds(),
+ v2->GetBounds());
+ }
+
+ nsIWidget* w1 = v1->GetWidget();
+ nsIWidget* w2 = v2->GetWidget();
+ if (((nullptr == w1) && (nullptr != w2)) ||
+ ((nullptr != w1) && (nullptr == w2))) {
+ ok = false;
+ LogVerifyMessage(k1, k2, "child widgets are not matched\n");
+ } else if (nullptr != w1) {
+ r1 = w1->GetBounds();
+ r2 = w2->GetBounds();
+ if (!r1.IsEqualEdges(r2)) {
+ LogVerifyMessage(k1, k2, "(widget rects)", r1.ToUnknownRect(),
+ r2.ToUnknownRect());
+ }
+ }
+ }
+ if (!ok && !(VerifyReflowFlags::All & gVerifyReflowFlags)) {
+ break;
+ }
+
+ // XXX Should perhaps compare their float managers.
+
+ // Compare the sub-trees too
+ if (!CompareTrees(aFirstPresContext, k1, aSecondPresContext, k2)) {
+ ok = false;
+ if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+ if (!ok && (!(VerifyReflowFlags::All & gVerifyReflowFlags))) {
+ break;
+ }
+
+ ++iterLists1;
+ ++iterLists2;
+ const bool lists1Done = iterLists1 == childLists1.end();
+ const bool lists2Done = iterLists2 == childLists2.end();
+ if (lists1Done != lists2Done ||
+ (!lists1Done && iterLists1->mID != iterLists2->mID)) {
+ if (!(VerifyReflowFlags::All & gVerifyReflowFlags)) {
+ ok = false;
+ }
+ LogVerifyMessage(kids1.FirstChild(), kids2.FirstChild(),
+ "child list names are not matched: ");
+ fprintf(stdout, "%s != %s\n",
+ !lists1Done ? ChildListName(iterLists1->mID) : "(null)",
+ !lists2Done ? ChildListName(iterLists2->mID) : "(null)");
+ break;
+ }
+ } while (ok && iterLists1 != childLists1.end());
+
+ return ok;
+}
+#endif
+
+#if 0
+static nsIFrame*
+FindTopFrame(nsIFrame* aRoot)
+{
+ if (aRoot) {
+ nsIContent* content = aRoot->GetContent();
+ if (content) {
+ nsAtom* tag;
+ content->GetTag(tag);
+ if (nullptr != tag) {
+ NS_RELEASE(tag);
+ return aRoot;
+ }
+ }
+
+ // Try one of the children
+ for (nsIFrame* kid : aRoot->PrincipalChildList()) {
+ nsIFrame* result = FindTopFrame(kid);
+ if (nullptr != result) {
+ return result;
+ }
+ }
+ }
+ return nullptr;
+}
+#endif
+
+#ifdef DEBUG
+
+// After an incremental reflow, we verify the correctness by doing a
+// full reflow into a fresh frame tree.
+bool PresShell::VerifyIncrementalReflow() {
+ if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
+ printf("Building Verification Tree...\n");
+ }
+
+ // Create a presentation context to view the new frame tree
+ RefPtr<nsPresContext> cx = new nsRootPresContext(
+ mDocument, mPresContext->IsPaginated()
+ ? nsPresContext::eContext_PrintPreview
+ : nsPresContext::eContext_Galley);
+ NS_ENSURE_TRUE(cx, false);
+
+ nsDeviceContext* dc = mPresContext->DeviceContext();
+ nsresult rv = cx->Init(dc);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Get our scrolling preference
+ nsView* rootView = mViewManager->GetRootView();
+ NS_ENSURE_TRUE(rootView->HasWidget(), false);
+ nsIWidget* parentWidget = rootView->GetWidget();
+
+ // Create a new view manager.
+ RefPtr<nsViewManager> vm = new nsViewManager();
+ NS_ENSURE_TRUE(vm, false);
+ rv = vm->Init(dc);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Create a child window of the parent that is our "root view/window"
+ // Create a view
+ nsRect tbounds = mPresContext->GetVisibleArea();
+ nsView* view = vm->CreateView(tbounds, nullptr);
+ NS_ENSURE_TRUE(view, false);
+
+ // now create the widget for the view
+ rv = view->CreateWidgetForParent(parentWidget, nullptr, true);
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // Setup hierarchical relationship in view manager
+ vm->SetRootView(view);
+
+ // Make the new presentation context the same size as our
+ // presentation context.
+ cx->SetVisibleArea(mPresContext->GetVisibleArea());
+
+ RefPtr<PresShell> presShell = mDocument->CreatePresShell(cx, vm);
+ NS_ENSURE_TRUE(presShell, false);
+
+ // Note that after we create the shell, we must make sure to destroy it
+ presShell->SetVerifyReflowEnable(
+ false); // turn off verify reflow while we're
+ // reflowing the test frame tree
+ vm->SetPresShell(presShell);
+ {
+ nsAutoCauseReflowNotifier crNotifier(this);
+ presShell->Initialize();
+ }
+ presShell->FlushPendingNotifications(FlushType::Layout);
+ presShell->SetVerifyReflowEnable(
+ true); // turn on verify reflow again now that
+ // we're done reflowing the test frame tree
+ // Force the non-primary presshell to unsuppress; it doesn't want to normally
+ // because it thinks it's hidden
+ presShell->mPaintingSuppressed = false;
+ if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
+ printf("Verification Tree built, comparing...\n");
+ }
+
+ // Now that the document has been reflowed, use its frame tree to
+ // compare against our frame tree.
+ nsIFrame* root1 = mFrameConstructor->GetRootFrame();
+ nsIFrame* root2 = presShell->GetRootFrame();
+ bool ok = CompareTrees(mPresContext, root1, cx, root2);
+ if (!ok && (VerifyReflowFlags::Noisy & gVerifyReflowFlags)) {
+ printf("Verify reflow failed, primary tree:\n");
+ root1->List(stdout);
+ printf("Verification tree:\n");
+ root2->List(stdout);
+ }
+
+# if 0
+ // Sample code for dumping page to png
+ // XXX Needs to be made more flexible
+ if (!ok) {
+ nsString stra;
+ static int num = 0;
+ stra.AppendLiteral("C:\\mozilla\\mozilla\\debug\\filea");
+ stra.AppendInt(num);
+ stra.AppendLiteral(".png");
+ gfxUtils::WriteAsPNG(presShell, stra);
+ nsString strb;
+ strb.AppendLiteral("C:\\mozilla\\mozilla\\debug\\fileb");
+ strb.AppendInt(num);
+ strb.AppendLiteral(".png");
+ gfxUtils::WriteAsPNG(presShell, strb);
+ ++num;
+ }
+# endif
+
+ presShell->EndObservingDocument();
+ presShell->Destroy();
+ if (VerifyReflowFlags::Noisy & gVerifyReflowFlags) {
+ printf("Finished Verifying Reflow...\n");
+ }
+
+ return ok;
+}
+
+// Layout debugging hooks
+void PresShell::ListComputedStyles(FILE* out, int32_t aIndent) {
+ nsIFrame* rootFrame = GetRootFrame();
+ if (rootFrame) {
+ rootFrame->Style()->List(out, aIndent);
+ }
+
+ // The root element's frame's ComputedStyle is the root of a separate tree.
+ Element* rootElement = mDocument->GetRootElement();
+ if (rootElement) {
+ nsIFrame* rootElementFrame = rootElement->GetPrimaryFrame();
+ if (rootElementFrame) {
+ rootElementFrame->Style()->List(out, aIndent);
+ }
+ }
+}
+#endif
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+void PresShell::ListStyleSheets(FILE* out, int32_t aIndent) {
+ auto ListStyleSheetsAtOrigin = [this, out, aIndent](StyleOrigin origin) {
+ int32_t sheetCount = StyleSet()->SheetCount(origin);
+ for (int32_t i = 0; i < sheetCount; ++i) {
+ StyleSet()->SheetAt(origin, i)->List(out, aIndent);
+ }
+ };
+
+ ListStyleSheetsAtOrigin(StyleOrigin::UserAgent);
+ ListStyleSheetsAtOrigin(StyleOrigin::User);
+ ListStyleSheetsAtOrigin(StyleOrigin::Author);
+}
+#endif
+
+//=============================================================
+//=============================================================
+//-- Debug Reflow Counts
+//=============================================================
+//=============================================================
+#ifdef MOZ_REFLOW_PERF
+//-------------------------------------------------------------
+void PresShell::DumpReflows() {
+ if (mReflowCountMgr) {
+ nsAutoCString uriStr;
+ if (mDocument) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ if (uri) {
+ uri->GetPathQueryRef(uriStr);
+ }
+ }
+ mReflowCountMgr->DisplayTotals(uriStr.get());
+ mReflowCountMgr->DisplayHTMLTotals(uriStr.get());
+ mReflowCountMgr->DisplayDiffsInTotals();
+ }
+}
+
+//-------------------------------------------------------------
+void PresShell::CountReflows(const char* aName, nsIFrame* aFrame) {
+ if (mReflowCountMgr) {
+ mReflowCountMgr->Add(aName, aFrame);
+ }
+}
+
+//-------------------------------------------------------------
+void PresShell::PaintCount(const char* aName, gfxContext* aRenderingContext,
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ const nsPoint& aOffset, uint32_t aColor) {
+ if (mReflowCountMgr) {
+ mReflowCountMgr->PaintCount(aName, aRenderingContext, aPresContext, aFrame,
+ aOffset, aColor);
+ }
+}
+
+//-------------------------------------------------------------
+void PresShell::SetPaintFrameCount(bool aPaintFrameCounts) {
+ if (mReflowCountMgr) {
+ mReflowCountMgr->SetPaintFrameCounts(aPaintFrameCounts);
+ }
+}
+
+bool PresShell::IsPaintingFrameCounts() {
+ if (mReflowCountMgr) return mReflowCountMgr->IsPaintingFrameCounts();
+ return false;
+}
+
+//------------------------------------------------------------------
+//-- Reflow Counter Classes Impls
+//------------------------------------------------------------------
+
+//------------------------------------------------------------------
+ReflowCounter::ReflowCounter(ReflowCountMgr* aMgr) : mMgr(aMgr) {
+ ClearTotals();
+ SetTotalsCache();
+}
+
+//------------------------------------------------------------------
+ReflowCounter::~ReflowCounter() = default;
+
+//------------------------------------------------------------------
+void ReflowCounter::ClearTotals() { mTotal = 0; }
+
+//------------------------------------------------------------------
+void ReflowCounter::SetTotalsCache() { mCacheTotal = mTotal; }
+
+//------------------------------------------------------------------
+void ReflowCounter::CalcDiffInTotals() { mCacheTotal = mTotal - mCacheTotal; }
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayTotals(const char* aStr) {
+ DisplayTotals(mTotal, aStr ? aStr : "Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayDiffTotals(const char* aStr) {
+ DisplayTotals(mCacheTotal, aStr ? aStr : "Diff Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayHTMLTotals(const char* aStr) {
+ DisplayHTMLTotals(mTotal, aStr ? aStr : "Totals");
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayTotals(uint32_t aTotal, const char* aTitle) {
+ // figure total
+ if (aTotal == 0) {
+ return;
+ }
+ ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
+
+ printf("%25s\t", aTitle);
+ printf("%d\t", aTotal);
+ if (gTots != this && aTotal > 0) {
+ gTots->Add(aTotal);
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCounter::DisplayHTMLTotals(uint32_t aTotal, const char* aTitle) {
+ if (aTotal == 0) {
+ return;
+ }
+
+ ReflowCounter* gTots = (ReflowCounter*)mMgr->LookUp(kGrandTotalsStr);
+ FILE* fd = mMgr->GetOutFile();
+ if (!fd) {
+ return;
+ }
+
+ fprintf(fd, "<tr><td><center>%s</center></td>", aTitle);
+ fprintf(fd, "<td><center>%d</center></td></tr>\n", aTotal);
+
+ if (gTots != this && aTotal > 0) {
+ gTots->Add(aTotal);
+ }
+}
+
+//------------------------------------------------------------------
+//-- ReflowCountMgr
+//------------------------------------------------------------------
+
+# define KEY_BUF_SIZE_FOR_PTR \
+ 24 // adequate char[] buffer to sprintf a pointer
+
+ReflowCountMgr::ReflowCountMgr() : mCounts(10), mIndiFrameCounts(10) {
+ mCycledOnce = false;
+ mDumpFrameCounts = false;
+ mDumpFrameByFrameCounts = false;
+ mPaintFrameByFrameCounts = false;
+}
+
+//------------------------------------------------------------------
+ReflowCountMgr::~ReflowCountMgr() = default;
+
+//------------------------------------------------------------------
+ReflowCounter* ReflowCountMgr::LookUp(const char* aName) {
+ return mCounts.Get(aName);
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::Add(const char* aName, nsIFrame* aFrame) {
+ NS_ASSERTION(aName != nullptr, "Name shouldn't be null!");
+
+ if (mDumpFrameCounts) {
+ auto* const counter = mCounts.GetOrInsertNew(aName, this);
+ counter->Add();
+ }
+
+ if ((mDumpFrameByFrameCounts || mPaintFrameByFrameCounts) &&
+ aFrame != nullptr) {
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aFrame);
+ auto* const counter =
+ mIndiFrameCounts
+ .LookupOrInsertWith(key,
+ [&aName, &aFrame, this]() {
+ auto counter =
+ MakeUnique<IndiReflowCounter>(this);
+ counter->mFrame = aFrame;
+ counter->mName.AssignASCII(aName);
+ return counter;
+ })
+ .get();
+ // this eliminates extra counts from super classes
+ if (counter && counter->mName.EqualsASCII(aName)) {
+ counter->mCount++;
+ counter->mCounter.Add(1);
+ }
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::PaintCount(const char* aName,
+ gfxContext* aRenderingContext,
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ const nsPoint& aOffset, uint32_t aColor) {
+ if (mPaintFrameByFrameCounts && aFrame != nullptr) {
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aFrame);
+ IndiReflowCounter* counter = mIndiFrameCounts.Get(key);
+ if (counter != nullptr && counter->mName.EqualsASCII(aName)) {
+ DrawTarget* drawTarget = aRenderingContext->GetDrawTarget();
+ int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+
+ aRenderingContext->Save();
+ gfxPoint devPixelOffset =
+ nsLayoutUtils::PointToGfxPoint(aOffset, appUnitsPerDevPixel);
+ aRenderingContext->SetMatrixDouble(
+ aRenderingContext->CurrentMatrixDouble().PreTranslate(
+ devPixelOffset));
+
+ // We don't care about the document language or user fonts here;
+ // just get a default Latin font.
+ nsFont font(StyleGenericFontFamily::Serif, Length::FromPixels(11));
+ nsFontMetrics::Params params;
+ params.language = nsGkAtoms::x_western;
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
+ RefPtr<nsFontMetrics> fm = aPresContext->GetMetricsFor(font, params);
+
+ char buf[16];
+ int len = SprintfLiteral(buf, "%d", counter->mCount);
+ nscoord x = 0, y = fm->MaxAscent();
+ nscoord width, height = fm->MaxHeight();
+ fm->SetTextRunRTL(false);
+ width = fm->GetWidth(buf, len, drawTarget);
+
+ sRGBColor color;
+ sRGBColor color2;
+ if (aColor != 0) {
+ color = sRGBColor::FromABGR(aColor);
+ color2 = sRGBColor(0.f, 0.f, 0.f);
+ } else {
+ gfx::Float rc = 0.f, gc = 0.f, bc = 0.f;
+ if (counter->mCount < 5) {
+ rc = 1.f;
+ gc = 1.f;
+ } else if (counter->mCount < 11) {
+ gc = 1.f;
+ } else {
+ rc = 1.f;
+ }
+ color = sRGBColor(rc, gc, bc);
+ color2 = sRGBColor(rc / 2, gc / 2, bc / 2);
+ }
+
+ nsRect rect(0, 0, width + 15, height + 15);
+ Rect devPxRect =
+ NSRectToSnappedRect(rect, appUnitsPerDevPixel, *drawTarget);
+ ColorPattern black(ToDeviceColor(sRGBColor::OpaqueBlack()));
+ drawTarget->FillRect(devPxRect, black);
+
+ aRenderingContext->SetColor(color2);
+ fm->DrawString(buf, len, x + 15, y + 15, aRenderingContext);
+ aRenderingContext->SetColor(color);
+ fm->DrawString(buf, len, x, y, aRenderingContext);
+
+ aRenderingContext->Restore();
+ }
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoGrandTotals() {
+ mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
+ if (!entry) {
+ entry.Insert(MakeUnique<ReflowCounter>(this));
+ } else {
+ entry.Data()->ClearTotals();
+ }
+ });
+
+ printf("\t\t\t\tTotal\n");
+ for (uint32_t i = 0; i < 78; i++) {
+ printf("-");
+ }
+ printf("\n");
+ for (const auto& entry : mCounts) {
+ entry.GetData()->DisplayTotals(entry.GetKey());
+ }
+}
+
+static void RecurseIndiTotals(
+ nsPresContext* aPresContext,
+ nsClassHashtable<nsCharPtrHashKey, IndiReflowCounter>& aHT,
+ nsIFrame* aParentFrame, int32_t aLevel) {
+ if (aParentFrame == nullptr) {
+ return;
+ }
+
+ char key[KEY_BUF_SIZE_FOR_PTR];
+ SprintfLiteral(key, "%p", (void*)aParentFrame);
+ IndiReflowCounter* counter = aHT.Get(key);
+ if (counter) {
+ counter->mHasBeenOutput = true;
+ char* name = ToNewCString(counter->mName);
+ for (int32_t i = 0; i < aLevel; i++) printf(" ");
+ printf("%s - %p [%d][", name, (void*)aParentFrame, counter->mCount);
+ printf("%d", counter->mCounter.GetTotal());
+ printf("]\n");
+ free(name);
+ }
+
+ for (nsIFrame* child : aParentFrame->PrincipalChildList()) {
+ RecurseIndiTotals(aPresContext, aHT, child, aLevel + 1);
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoIndiTotalsTree() {
+ printf("\n------------------------------------------------\n");
+ printf("-- Individual Frame Counts\n");
+ printf("------------------------------------------------\n");
+
+ if (mPresShell) {
+ nsIFrame* rootFrame = mPresShell->GetRootFrame();
+ RecurseIndiTotals(mPresContext, mIndiFrameCounts, rootFrame, 0);
+ printf("------------------------------------------------\n");
+ printf("-- Individual Counts of Frames not in Root Tree\n");
+ printf("------------------------------------------------\n");
+ for (const auto& counter : mIndiFrameCounts.Values()) {
+ if (!counter->mHasBeenOutput) {
+ char* name = ToNewCString(counter->mName);
+ printf("%s - %p [%d][", name, (void*)counter->mFrame,
+ counter->mCount);
+ printf("%d", counter->mCounter.GetTotal());
+ printf("]\n");
+ free(name);
+ }
+ }
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DoGrandHTMLTotals() {
+ mCounts.WithEntryHandle(kGrandTotalsStr, [this](auto&& entry) {
+ if (!entry) {
+ entry.Insert(MakeUnique<ReflowCounter>(this));
+ } else {
+ entry.Data()->ClearTotals();
+ }
+ });
+
+ static const char* title[] = {"Class", "Reflows"};
+ fprintf(mFD, "<tr>");
+ for (uint32_t i = 0; i < ArrayLength(title); i++) {
+ fprintf(mFD, "<td><center><b>%s<b></center></td>", title[i]);
+ }
+ fprintf(mFD, "</tr>\n");
+
+ for (const auto& entry : mCounts) {
+ entry.GetData()->DisplayHTMLTotals(entry.GetKey());
+ }
+}
+
+//------------------------------------
+void ReflowCountMgr::DisplayTotals(const char* aStr) {
+# ifdef DEBUG_rods
+ printf("%s\n", aStr ? aStr : "No name");
+# endif
+ if (mDumpFrameCounts) {
+ DoGrandTotals();
+ }
+ if (mDumpFrameByFrameCounts) {
+ DoIndiTotalsTree();
+ }
+}
+//------------------------------------
+void ReflowCountMgr::DisplayHTMLTotals(const char* aStr) {
+# ifdef WIN32x // XXX NOT XP!
+ char name[1024];
+
+ char* sptr = strrchr(aStr, '/');
+ if (sptr) {
+ sptr++;
+ strcpy(name, sptr);
+ char* eptr = strrchr(name, '.');
+ if (eptr) {
+ *eptr = 0;
+ }
+ strcat(name, "_stats.html");
+ }
+ mFD = fopen(name, "w");
+ if (mFD) {
+ fprintf(mFD, "<html><head><title>Reflow Stats</title></head><body>\n");
+ const char* title = aStr ? aStr : "No name";
+ fprintf(mFD,
+ "<center><b>%s</b><br><table border=1 "
+ "style=\"background-color:#e0e0e0\">",
+ title);
+ DoGrandHTMLTotals();
+ fprintf(mFD, "</center></table>\n");
+ fprintf(mFD, "</body></html>\n");
+ fclose(mFD);
+ mFD = nullptr;
+ }
+# endif // not XP!
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::ClearTotals() {
+ for (const auto& data : mCounts.Values()) {
+ data->ClearTotals();
+ }
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::ClearGrandTotals() {
+ mCounts.WithEntryHandle(kGrandTotalsStr, [&](auto&& entry) {
+ if (!entry) {
+ entry.Insert(MakeUnique<ReflowCounter>(this));
+ } else {
+ entry.Data()->ClearTotals();
+ entry.Data()->SetTotalsCache();
+ }
+ });
+}
+
+//------------------------------------------------------------------
+void ReflowCountMgr::DisplayDiffsInTotals() {
+ if (mCycledOnce) {
+ printf("Differences\n");
+ for (int32_t i = 0; i < 78; i++) {
+ printf("-");
+ }
+ printf("\n");
+ ClearGrandTotals();
+ }
+
+ for (const auto& entry : mCounts) {
+ if (mCycledOnce) {
+ entry.GetData()->CalcDiffInTotals();
+ entry.GetData()->DisplayDiffTotals(entry.GetKey());
+ }
+ entry.GetData()->SetTotalsCache();
+ }
+
+ mCycledOnce = true;
+}
+
+#endif // MOZ_REFLOW_PERF
+
+nsIFrame* PresShell::GetAbsoluteContainingBlock(nsIFrame* aFrame) {
+ return FrameConstructor()->GetAbsoluteContainingBlock(
+ aFrame, nsCSSFrameConstructor::ABS_POS);
+}
+
+void PresShell::ActivenessMaybeChanged() {
+ if (!mDocument) {
+ return;
+ }
+ SetIsActive(ComputeActiveness());
+}
+
+// A PresShell being active means that it is visible (or close to be visible, if
+// the front-end is warming it). That means that when it is active we always
+// tick its refresh driver at full speed if needed.
+//
+// Image documents behave specially in the sense that they are always "active"
+// and never "in the active tab". However these documents tick manually so
+// there's not much to worry about there.
+bool PresShell::ComputeActiveness() const {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ ("PresShell::ComputeActiveness(%s, %d)\n",
+ mDocument->GetDocumentURI()
+ ? mDocument->GetDocumentURI()->GetSpecOrDefault().get()
+ : "(no uri)",
+ mIsActive));
+
+ Document* doc = mDocument;
+
+ if (doc->IsBeingUsedAsImage()) {
+ // Documents used as an image can remain active. They do not tick their
+ // refresh driver if not painted, and they can't run script or such so they
+ // can't really observe much else.
+ //
+ // Image docs can be displayed in multiple docs at the same time so the "in
+ // active tab" bool doesn't make much sense for them.
+ return true;
+ }
+
+ if (Document* displayDoc = doc->GetDisplayDocument()) {
+ // Ok, we're an external resource document -- we need to use our display
+ // document's docshell to determine "IsActive" status, since we lack
+ // a browsing context of our own.
+ MOZ_ASSERT(!doc->GetBrowsingContext(),
+ "external resource doc shouldn't have its own BC");
+ doc = displayDoc;
+ }
+
+ BrowsingContext* bc = doc->GetBrowsingContext();
+ const bool inActiveTab = bc && bc->IsActive();
+
+ MOZ_LOG(gLog, LogLevel::Debug,
+ (" > BrowsingContext %p active: %d", bc, inActiveTab));
+
+ Document* root = nsContentUtils::GetInProcessSubtreeRootDocument(doc);
+ if (auto* browserChild = BrowserChild::GetFrom(root->GetDocShell())) {
+ // We might want to activate a tab even though the browsing-context is not
+ // active if the BrowserChild is considered visible. This serves two
+ // purposes:
+ //
+ // * For top-level tabs, we use this for tab warming. The browsing-context
+ // might still be inactive, but we want to activate the pres shell and
+ // the refresh driver.
+ //
+ // * For oop iframes, we do want to throttle them if they're not visible.
+ //
+ // TODO(emilio): Consider unifying the in-process vs. fission iframe
+ // throttling code (in-process throttling for non-visible iframes lives
+ // right now in Document::ShouldThrottleFrameRequests(), but that only
+ // throttles rAF).
+ if (!browserChild->IsVisible()) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ (" > BrowserChild %p is not visible", browserChild));
+ return false;
+ }
+
+ // If the browser is visible but just due to be preserving layers
+ // artificially, we do want to fall back to the browsing context activeness
+ // instead. Otherwise we do want to be active for the use cases above.
+ if (!browserChild->IsPreservingLayers()) {
+ MOZ_LOG(gLog, LogLevel::Debug,
+ (" > BrowserChild %p is visible and not preserving layers",
+ browserChild));
+ return true;
+ }
+ MOZ_LOG(
+ gLog, LogLevel::Debug,
+ (" > BrowserChild %p is visible and preserving layers", browserChild));
+ }
+ return inActiveTab;
+}
+
+void PresShell::SetIsActive(bool aIsActive) {
+ MOZ_ASSERT(mDocument, "should only be called with a document");
+
+ const bool activityChanged = mIsActive != aIsActive;
+
+ mIsActive = aIsActive;
+
+ nsPresContext* presContext = GetPresContext();
+ if (presContext &&
+ presContext->RefreshDriver()->GetPresContext() == presContext) {
+ presContext->RefreshDriver()->SetActivity(aIsActive);
+ }
+
+ if (activityChanged) {
+ // Propagate state-change to my resource documents' PresShells and other
+ // subdocuments.
+ //
+ // Note that it is fine to not propagate to fission iframes. Those will
+ // become active / inactive as needed as a result of they getting painted /
+ // not painted eventually.
+ auto recurse = [aIsActive](Document& aSubDoc) {
+ if (PresShell* presShell = aSubDoc.GetPresShell()) {
+ presShell->SetIsActive(aIsActive);
+ }
+ return CallState::Continue;
+ };
+ mDocument->EnumerateExternalResources(recurse);
+ mDocument->EnumerateSubDocuments(recurse);
+ }
+
+ UpdateImageLockingState();
+
+ if (activityChanged) {
+#if defined(MOZ_WIDGET_ANDROID)
+ if (!aIsActive && presContext &&
+ presContext->IsRootContentDocumentCrossProcess()) {
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) {
+ // Reset the dynamic toolbar offset state.
+ presContext->UpdateDynamicToolbarOffset(0);
+ }
+ }
+#endif
+ }
+
+ if (aIsActive) {
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->PresShellActivated(this);
+ }
+#endif
+ if (nsIFrame* rootFrame = GetRootFrame()) {
+ rootFrame->SchedulePaint();
+ }
+ }
+}
+
+RefPtr<MobileViewportManager> PresShell::GetMobileViewportManager() const {
+ return mMobileViewportManager;
+}
+
+Maybe<MobileViewportManager::ManagerType> UseMobileViewportManager(
+ PresShell* aPresShell, Document* aDocument) {
+ // If we're not using APZ, we won't be able to zoom, so there is no
+ // point in having an MVM.
+ if (nsPresContext* presContext = aPresShell->GetPresContext()) {
+ if (nsIWidget* widget = presContext->GetNearestWidget()) {
+ if (!widget->AsyncPanZoomEnabled()) {
+ return Nothing();
+ }
+ }
+ }
+ if (nsLayoutUtils::ShouldHandleMetaViewport(aDocument)) {
+ return Some(MobileViewportManager::ManagerType::VisualAndMetaViewport);
+ }
+ if (StaticPrefs::apz_mvm_force_enabled() ||
+ nsLayoutUtils::AllowZoomingForDocument(aDocument)) {
+ return Some(MobileViewportManager::ManagerType::VisualViewportOnly);
+ }
+ return Nothing();
+}
+
+void PresShell::MaybeRecreateMobileViewportManager(bool aAfterInitialization) {
+ // Determine if we require a MobileViewportManager, and what kind if so. We
+ // need one any time we allow resolution zooming for a document, and any time
+ // we want to obey <meta name="viewport"> tags for it.
+ Maybe<MobileViewportManager::ManagerType> mvmType =
+ UseMobileViewportManager(this, mDocument);
+
+ if (mvmType.isNothing() && !mMobileViewportManager) {
+ // We don't need one and don't have it. So we're done.
+ return;
+ }
+ if (mvmType && mMobileViewportManager &&
+ *mvmType == mMobileViewportManager->GetManagerType()) {
+ // We need one and we have one of the correct type, so we're done.
+ return;
+ }
+
+ if (mMobileViewportManager) {
+ // We have one, but we need to either destroy it completely to replace it
+ // with another one of the correct type. So either way, let's destroy the
+ // one we have.
+ mMobileViewportManager->Destroy();
+ mMobileViewportManager = nullptr;
+ mMVMContext = nullptr;
+
+ ResetVisualViewportSize();
+
+ // After we clear out the MVM and the MVMContext, also reset the
+ // resolution to its pre-MVM value.
+ SetResolutionAndScaleTo(mDocument->GetSavedResolutionBeforeMVM(),
+ ResolutionChangeOrigin::MainThreadRestore);
+
+ if (aAfterInitialization) {
+ // Force a reflow to our correct view manager size.
+ ForceResizeReflowWithCurrentDimensions();
+ }
+ }
+
+ if (mvmType) {
+ // Let's create the MVM of the type that we need. At this point we shouldn't
+ // have one.
+ MOZ_ASSERT(!mMobileViewportManager);
+
+ if (mPresContext->IsRootContentDocumentCrossProcess()) {
+ // Store the resolution so we can restore to this resolution when
+ // the MVM is destroyed.
+ mDocument->SetSavedResolutionBeforeMVM(mResolution.valueOr(1.0f));
+
+ mMVMContext = new GeckoMVMContext(mDocument, this);
+ mMobileViewportManager = new MobileViewportManager(mMVMContext, *mvmType);
+ if (MOZ_UNLIKELY(
+ MOZ_LOG_TEST(MobileViewportManager::gLog, LogLevel::Debug))) {
+ nsIURI* uri = mDocument->GetDocumentURI();
+ MOZ_LOG(MobileViewportManager::gLog, LogLevel::Debug,
+ ("Created MVM %p (type %d) for URI %s",
+ mMobileViewportManager.get(), (int)*mvmType,
+ uri ? uri->GetSpecOrDefault().get() : "(null)"));
+ }
+
+ if (aAfterInitialization) {
+ // Setting the initial viewport will trigger a reflow.
+ mMobileViewportManager->SetInitialViewport();
+ }
+ }
+ }
+}
+
+bool PresShell::UsesMobileViewportSizing() const {
+ return mMobileViewportManager != nullptr &&
+ nsLayoutUtils::ShouldHandleMetaViewport(mDocument);
+}
+
+/*
+ * Determines the current image locking state. Called when one of the
+ * dependent factors changes.
+ */
+void PresShell::UpdateImageLockingState() {
+ // We're locked if we're both thawed and active.
+ const bool locked = !mFrozen && mIsActive;
+ auto* tracker = mDocument->ImageTracker();
+ if (locked == tracker->GetLockingState()) {
+ return;
+ }
+
+ tracker->SetLockingState(locked);
+ if (locked) {
+ // Request decodes for visible image frames; we want to start decoding as
+ // quickly as possible when we get foregrounded to minimize flashing.
+ for (const auto& key : mApproximatelyVisibleFrames) {
+ if (nsImageFrame* imageFrame = do_QueryFrame(key)) {
+ imageFrame->MaybeDecodeForPredictedSize();
+ }
+ }
+ }
+}
+
+PresShell* PresShell::GetRootPresShell() const {
+ if (mPresContext) {
+ nsPresContext* rootPresContext = mPresContext->GetRootPresContext();
+ if (rootPresContext) {
+ return rootPresContext->PresShell();
+ }
+ }
+ return nullptr;
+}
+
+void PresShell::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
+ MallocSizeOf mallocSizeOf = aSizes.mState.mMallocSizeOf;
+ mFrameArena.AddSizeOfExcludingThis(aSizes, Arena::ArenaKind::PresShell);
+ aSizes.mLayoutPresShellSize += mallocSizeOf(this);
+ if (mCaret) {
+ aSizes.mLayoutPresShellSize += mCaret->SizeOfIncludingThis(mallocSizeOf);
+ }
+ aSizes.mLayoutPresShellSize +=
+ mApproximatelyVisibleFrames.ShallowSizeOfExcludingThis(mallocSizeOf) +
+ mFramesToDirty.ShallowSizeOfExcludingThis(mallocSizeOf) +
+ mPendingScrollAnchorSelection.ShallowSizeOfExcludingThis(mallocSizeOf) +
+ mPendingScrollAnchorAdjustment.ShallowSizeOfExcludingThis(mallocSizeOf);
+
+ aSizes.mLayoutTextRunsSize += SizeOfTextRuns(mallocSizeOf);
+
+ aSizes.mLayoutPresContextSize +=
+ mPresContext->SizeOfIncludingThis(mallocSizeOf);
+
+ mFrameConstructor->AddSizeOfIncludingThis(aSizes);
+}
+
+size_t PresShell::SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const {
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (!rootFrame) {
+ return 0;
+ }
+
+ // clear the TEXT_RUN_MEMORY_ACCOUNTED flags
+ nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, nullptr,
+ /* clear = */ true);
+
+ // collect the total memory in use for textruns
+ return nsLayoutUtils::SizeOfTextRunsForFrames(rootFrame, aMallocSizeOf,
+ /* clear = */ false);
+}
+
+void PresShell::MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty) {
+ nsIFrame* rootFrame = mFrameConstructor->GetRootFrame();
+ if (rootFrame) {
+ const nsFrameList& childList =
+ rootFrame->GetChildList(FrameChildListID::Fixed);
+ for (nsIFrame* childFrame : childList) {
+ FrameNeedsReflow(childFrame, aIntrinsicDirty, NS_FRAME_IS_DIRTY);
+ }
+ }
+}
+
+static void AppendSubtree(nsIDocShell* aDocShell,
+ nsTArray<nsCOMPtr<nsIDocumentViewer>>& aArray) {
+ if (nsCOMPtr<nsIDocumentViewer> viewer = aDocShell->GetDocViewer()) {
+ aArray.AppendElement(viewer);
+ }
+
+ int32_t n = aDocShell->GetInProcessChildCount();
+ for (int32_t i = 0; i < n; i++) {
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ aDocShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
+ if (childItem) {
+ nsCOMPtr<nsIDocShell> child(do_QueryInterface(childItem));
+ AppendSubtree(child, aArray);
+ }
+ }
+}
+
+void PresShell::MaybeReflowForInflationScreenSizeChange() {
+ nsPresContext* pc = GetPresContext();
+ const bool fontInflationWasEnabled = FontSizeInflationEnabled();
+ RecomputeFontSizeInflationEnabled();
+ bool changed = false;
+ if (FontSizeInflationEnabled() && FontSizeInflationMinTwips() != 0) {
+ pc->ScreenSizeInchesForFontInflation(&changed);
+ }
+
+ changed = changed || fontInflationWasEnabled != FontSizeInflationEnabled();
+ if (!changed) {
+ return;
+ }
+ if (nsCOMPtr<nsIDocShell> docShell = pc->GetDocShell()) {
+ nsTArray<nsCOMPtr<nsIDocumentViewer>> array;
+ AppendSubtree(docShell, array);
+ for (uint32_t i = 0, iEnd = array.Length(); i < iEnd; ++i) {
+ nsCOMPtr<nsIDocumentViewer> viewer = array[i];
+ if (RefPtr<PresShell> descendantPresShell = viewer->GetPresShell()) {
+ nsIFrame* rootFrame = descendantPresShell->GetRootFrame();
+ if (rootFrame) {
+ descendantPresShell->FrameNeedsReflow(
+ rootFrame, IntrinsicDirty::FrameAncestorsAndDescendants,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ }
+ }
+}
+
+void PresShell::CompleteChangeToVisualViewportSize() {
+ // This can get called during reflow, if the caller wants to get the latest
+ // visual viewport size after scrollbars have been added/removed. In such a
+ // case, we don't need to mark things as dirty because the things that we
+ // would mark dirty either just got updated (the root scrollframe's
+ // scrollbars), or will be laid out later during this reflow cycle (fixed-pos
+ // items). Callers that update the visual viewport during a reflow are
+ // responsible for maintaining these invariants.
+ if (!mIsReflowing) {
+ if (nsIScrollableFrame* rootScrollFrame =
+ GetRootScrollFrameAsScrollable()) {
+ rootScrollFrame->MarkScrollbarsDirtyForReflow();
+ }
+ MarkFixedFramesForReflow(IntrinsicDirty::None);
+ }
+
+ MaybeReflowForInflationScreenSizeChange();
+
+ if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+ window->VisualViewport()->PostResizeEvent();
+ }
+}
+
+void PresShell::SetVisualViewportSize(nscoord aWidth, nscoord aHeight) {
+ MOZ_ASSERT(aWidth >= 0.0 && aHeight >= 0.0);
+
+ if (!mVisualViewportSizeSet || mVisualViewportSize.width != aWidth ||
+ mVisualViewportSize.height != aHeight) {
+ mVisualViewportSizeSet = true;
+ mVisualViewportSize.width = aWidth;
+ mVisualViewportSize.height = aHeight;
+
+ CompleteChangeToVisualViewportSize();
+ }
+}
+
+void PresShell::ResetVisualViewportSize() {
+ if (mVisualViewportSizeSet) {
+ mVisualViewportSizeSet = false;
+ mVisualViewportSize.width = 0;
+ mVisualViewportSize.height = 0;
+
+ CompleteChangeToVisualViewportSize();
+ }
+}
+
+bool PresShell::SetVisualViewportOffset(const nsPoint& aScrollOffset,
+ const nsPoint& aPrevLayoutScrollPos) {
+ nsPoint newOffset = aScrollOffset;
+ nsIScrollableFrame* rootScrollFrame = GetRootScrollFrameAsScrollable();
+ if (rootScrollFrame) {
+ // See the comment in nsHTMLScrollFrame::Reflow above the call to
+ // SetVisualViewportOffset for why we need to do this.
+ nsRect scrollRange = rootScrollFrame->GetScrollRangeForUserInputEvents();
+ if (!scrollRange.Contains(newOffset)) {
+ newOffset.x = std::min(newOffset.x, scrollRange.XMost());
+ newOffset.x = std::max(newOffset.x, scrollRange.x);
+ newOffset.y = std::min(newOffset.y, scrollRange.YMost());
+ newOffset.y = std::max(newOffset.y, scrollRange.y);
+ }
+ }
+
+ // Careful here not to call GetVisualViewportOffset to get the previous visual
+ // viewport offset because if mVisualViewportOffset is nothing then we'll get
+ // the layout scroll position directly from the scroll frame and it has likely
+ // already been updated.
+ nsPoint prevOffset = aPrevLayoutScrollPos;
+ if (mVisualViewportOffset.isSome()) {
+ prevOffset = *mVisualViewportOffset;
+ }
+ if (prevOffset == newOffset) {
+ return false;
+ }
+
+ mVisualViewportOffset = Some(newOffset);
+
+ if (auto* window = nsGlobalWindowInner::Cast(mDocument->GetInnerWindow())) {
+ window->VisualViewport()->PostScrollEvent(prevOffset, aPrevLayoutScrollPos);
+ }
+
+ if (IsVisualViewportSizeSet() && rootScrollFrame) {
+ rootScrollFrame->Anchor()->UserScrolled();
+ }
+
+ if (gfxPlatform::UseDesktopZoomingScrollbars()) {
+ if (nsIScrollableFrame* rootScrollFrame =
+ GetRootScrollFrameAsScrollable()) {
+ rootScrollFrame->UpdateScrollbarPosition();
+ }
+ }
+
+ return true;
+}
+
+void PresShell::ResetVisualViewportOffset() { mVisualViewportOffset.reset(); }
+
+void PresShell::ScrollToVisual(const nsPoint& aVisualViewportOffset,
+ FrameMetrics::ScrollOffsetUpdateType aUpdateType,
+ ScrollMode aMode) {
+ MOZ_ASSERT(aMode == ScrollMode::Instant || aMode == ScrollMode::SmoothMsd);
+
+ if (aMode == ScrollMode::SmoothMsd) {
+ if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
+ if (sf->SmoothScrollVisual(aVisualViewportOffset, aUpdateType)) {
+ return;
+ }
+ }
+ }
+
+ // If the caller asked for instant scroll, or if we failed
+ // to do a smooth scroll, do an instant scroll.
+ SetPendingVisualScrollUpdate(aVisualViewportOffset, aUpdateType);
+}
+
+void PresShell::SetPendingVisualScrollUpdate(
+ const nsPoint& aVisualViewportOffset,
+ FrameMetrics::ScrollOffsetUpdateType aUpdateType) {
+ mPendingVisualScrollUpdate =
+ Some(VisualScrollUpdate{aVisualViewportOffset, aUpdateType});
+
+ // The pending update is picked up during the next paint.
+ // Schedule a paint to make sure one will happen.
+ if (nsIFrame* rootFrame = GetRootFrame()) {
+ rootFrame->SchedulePaint();
+ }
+}
+
+void PresShell::ClearPendingVisualScrollUpdate() {
+ if (mPendingVisualScrollUpdate && mPendingVisualScrollUpdate->mAcknowledged) {
+ mPendingVisualScrollUpdate = mozilla::Nothing();
+ }
+}
+
+void PresShell::AcknowledgePendingVisualScrollUpdate() {
+ MOZ_ASSERT(mPendingVisualScrollUpdate);
+ mPendingVisualScrollUpdate->mAcknowledged = true;
+}
+
+nsPoint PresShell::GetVisualViewportOffsetRelativeToLayoutViewport() const {
+ return GetVisualViewportOffset() - GetLayoutViewportOffset();
+}
+
+nsPoint PresShell::GetLayoutViewportOffset() const {
+ nsPoint result;
+ if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
+ result = sf->GetScrollPosition();
+ }
+ return result;
+}
+
+nsSize PresShell::GetLayoutViewportSize() const {
+ nsSize result;
+ if (nsIScrollableFrame* sf = GetRootScrollFrameAsScrollable()) {
+ result = sf->GetScrollPortRect().Size();
+ }
+ return result;
+}
+
+nsSize PresShell::GetVisualViewportSizeUpdatedByDynamicToolbar() const {
+ NS_ASSERTION(mVisualViewportSizeSet,
+ "asking for visual viewport size when its not set?");
+ if (!mMobileViewportManager) {
+ return mVisualViewportSize;
+ }
+
+ MOZ_ASSERT(GetDynamicToolbarState() == DynamicToolbarState::InTransition ||
+ GetDynamicToolbarState() == DynamicToolbarState::Collapsed);
+
+ nsSize sizeUpdatedByDynamicToolbar =
+ mMobileViewportManager->GetVisualViewportSizeUpdatedByDynamicToolbar();
+ return sizeUpdatedByDynamicToolbar == nsSize() ? mVisualViewportSize
+ : sizeUpdatedByDynamicToolbar;
+}
+
+void PresShell::RecomputeFontSizeInflationEnabled() {
+ mFontSizeInflationEnabled = DetermineFontSizeInflationState();
+}
+
+bool PresShell::DetermineFontSizeInflationState() {
+ MOZ_ASSERT(mPresContext, "our pres context should not be null");
+ if (mPresContext->IsChrome()) {
+ return false;
+ }
+
+ if (FontSizeInflationEmPerLine() == 0 && FontSizeInflationMinTwips() == 0) {
+ return false;
+ }
+
+ // Force-enabling font inflation always trumps the heuristics here.
+ if (!FontSizeInflationForceEnabled()) {
+ if (BrowserChild* tab = BrowserChild::GetFrom(this)) {
+ // We're in a child process. Cancel inflation if we're not
+ // async-pan zoomed.
+ if (!tab->AsyncPanZoomEnabled()) {
+ return false;
+ }
+ } else if (XRE_IsParentProcess()) {
+ // We're in the master process. Cancel inflation if it's been
+ // explicitly disabled.
+ if (FontSizeInflationDisabledInMasterProcess()) {
+ return false;
+ }
+ }
+ }
+
+ Maybe<LayoutDeviceIntSize> displaySize;
+ // The MVM already caches the top-level content viewer size and is therefore
+ // the fastest way of getting that data.
+ if (mPresContext->IsRootContentDocumentCrossProcess()) {
+ if (mMobileViewportManager) {
+ displaySize = Some(mMobileViewportManager->DisplaySize());
+ }
+ } else if (PresShell* rootPresShell = GetRootPresShell()) {
+ // With any luck, we can get at the root content document without any cross-
+ // process shenanigans.
+ if (auto mvm = rootPresShell->GetMobileViewportManager()) {
+ displaySize = Some(mvm->DisplaySize());
+ }
+ }
+
+ if (!displaySize) {
+ // Unfortunately, it looks like the root content document lives in a
+ // different process. For consistency's sake it would be best to always use
+ // the content viewer size of the root content document, but it's not worth
+ // the effort, because this only makes a difference in the case of pages
+ // with an explicitly sized viewport (neither "width=device-width" nor a
+ // completely missing viewport tag) being loaded within a frame, which is
+ // hopefully a relatively exotic case.
+ // More to the point, these viewport size and zoom-based calculations don't
+ // really make sense for frames anyway, so instead of creating a way to
+ // access the content viewer size of the top level document cross-process,
+ // we probably rather want frames to simply inherit the font inflation state
+ // of their top-level parent and should therefore invest any time spent on
+ // getting things to work cross-process into that (bug 1724311).
+
+ // Until we get around to that though, we just use the content viewer size
+ // of however high we can get within the same process.
+
+ // (This also serves as a fallback code path if the MVM isn't available,
+ // e.g. when debugging in non-e10s mode on Desktop.)
+ nsPresContext* topContext =
+ mPresContext->GetInProcessRootContentDocumentPresContext();
+ LayoutDeviceIntSize result;
+ if (!nsLayoutUtils::GetDocumentViewerSize(topContext, result)) {
+ return false;
+ }
+ displaySize = Some(result);
+ }
+
+ ScreenIntSize screenSize = ViewAs<ScreenPixel>(
+ displaySize.value(),
+ PixelCastJustification::LayoutDeviceIsScreenForBounds);
+ nsViewportInfo vInf = GetDocument()->GetViewportInfo(screenSize);
+
+ CSSToScreenScale defaultScale =
+ mPresContext->CSSToDevPixelScale() * LayoutDeviceToScreenScale(1.0);
+
+ if (vInf.GetDefaultZoom() >= defaultScale || vInf.IsAutoSizeEnabled()) {
+ return false;
+ }
+
+ return true;
+}
+
+static nsIWidget* GetPresContextContainerWidget(nsPresContext* aPresContext) {
+ nsCOMPtr<nsISupports> container = aPresContext->Document()->GetContainer();
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
+ if (!baseWindow) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIWidget> mainWidget;
+ baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
+ return mainWidget;
+}
+
+static bool IsTopLevelWidget(nsIWidget* aWidget) {
+ using WindowType = mozilla::widget::WindowType;
+
+ auto windowType = aWidget->GetWindowType();
+ return windowType == WindowType::TopLevel ||
+ windowType == WindowType::Dialog || windowType == WindowType::Popup ||
+ windowType == WindowType::Sheet;
+}
+
+PresShell::WindowSizeConstraints PresShell::GetWindowSizeConstraints() {
+ nsSize minSize(0, 0);
+ nsSize maxSize(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
+ if (!rootFrame || !mPresContext) {
+ return {minSize, maxSize};
+ }
+ const auto* pos = rootFrame->StylePosition();
+ if (pos->mMinWidth.ConvertsToLength()) {
+ minSize.width = pos->mMinWidth.ToLength();
+ }
+ if (pos->mMinHeight.ConvertsToLength()) {
+ minSize.height = pos->mMinHeight.ToLength();
+ }
+ if (pos->mMaxWidth.ConvertsToLength()) {
+ maxSize.width = pos->mMaxWidth.ToLength();
+ }
+ if (pos->mMaxHeight.ConvertsToLength()) {
+ maxSize.height = pos->mMaxHeight.ToLength();
+ }
+ return {minSize, maxSize};
+}
+
+void PresShell::SyncWindowProperties(bool aSync) {
+ nsView* view = mViewManager->GetRootView();
+ if (!view || !view->HasWidget()) {
+ return;
+ }
+ RefPtr pc = mPresContext;
+ if (!pc) {
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(pc);
+ if (!windowWidget || !IsTopLevelWidget(windowWidget)) {
+ return;
+ }
+
+ nsIFrame* rootFrame = FrameConstructor()->GetRootElementStyleFrame();
+ if (!rootFrame) {
+ return;
+ }
+
+ if (!aSync) {
+ view->SetNeedsWindowPropertiesSync();
+ return;
+ }
+
+ AutoWeakFrame weak(rootFrame);
+ if (!GetRootScrollFrame()) {
+ // Scrollframes use native widgets which don't work well with
+ // translucent windows, at least in Windows XP. So if the document
+ // has a root scrollrame it's useless to try to make it transparent,
+ // we'll just get something broken.
+ // We can change this to allow translucent toplevel HTML documents
+ // (e.g. to do something like Dashboard widgets), once we
+ // have broad support for translucent scrolled documents, but be
+ // careful because apparently some Firefox extensions expect
+ // openDialog("something.html") to produce an opaque window
+ // even if the HTML doesn't have a background-color set.
+ auto* canvas = GetCanvasFrame();
+ widget::TransparencyMode mode = nsLayoutUtils::GetFrameTransparency(
+ canvas ? canvas : rootFrame, rootFrame);
+ windowWidget->SetTransparencyMode(mode);
+
+ // For macOS, apply color scheme overrides to the top level window widget.
+ if (auto scheme = pc->GetOverriddenOrEmbedderColorScheme()) {
+ windowWidget->SetColorScheme(scheme);
+ }
+ }
+
+ if (!weak.IsAlive()) {
+ return;
+ }
+
+ const auto& constraints = GetWindowSizeConstraints();
+ nsContainerFrame::SetSizeConstraints(pc, windowWidget, constraints.mMinSize,
+ constraints.mMaxSize);
+}
+
+nsresult PresShell::HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal) {
+ *aRetVal = false;
+ return NS_OK;
+}
+
+void PresShell::NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet,
+ uint32_t aSheetType) {
+ switch (aSheetType) {
+ case nsIStyleSheetService::AGENT_SHEET:
+ AddAgentSheet(aSheet);
+ break;
+ case nsIStyleSheetService::USER_SHEET:
+ AddUserSheet(aSheet);
+ break;
+ case nsIStyleSheetService::AUTHOR_SHEET:
+ AddAuthorSheet(aSheet);
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected aSheetType value");
+ break;
+ }
+}
+
+void PresShell::NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
+ uint32_t aSheetType) {
+ StyleSet()->RemoveStyleSheet(*aSheet);
+ mDocument->ApplicableStylesChanged();
+}
+
+nsIContent* PresShell::EventHandler::GetOverrideClickTarget(
+ WidgetGUIEvent* aGUIEvent, nsIFrame* aFrame) {
+ if (aGUIEvent->mMessage != eMouseUp) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(aGUIEvent->mClass == eMouseEventClass);
+ WidgetMouseEvent* mouseEvent = aGUIEvent->AsMouseEvent();
+
+ uint32_t flags = 0;
+ RelativeTo relativeTo{aFrame};
+ nsPoint eventPoint =
+ nsLayoutUtils::GetEventCoordinatesRelativeTo(aGUIEvent, relativeTo);
+ if (mouseEvent->mIgnoreRootScrollFrame) {
+ flags |= INPUT_IGNORE_ROOT_SCROLL_FRAME;
+ }
+
+ nsIFrame* target =
+ FindFrameTargetedByInputEvent(aGUIEvent, relativeTo, eventPoint, flags);
+ if (!target) {
+ return nullptr;
+ }
+
+ nsIContent* overrideClickTarget = target->GetContent();
+ while (overrideClickTarget && !overrideClickTarget->IsElement()) {
+ overrideClickTarget = overrideClickTarget->GetFlattenedTreeParent();
+ }
+ return overrideClickTarget;
+}
+
+/******************************************************************************
+ * PresShell::EventHandler::EventTargetData
+ ******************************************************************************/
+
+void PresShell::EventHandler::EventTargetData::SetFrameAndComputePresShell(
+ nsIFrame* aFrameToHandleEvent) {
+ if (aFrameToHandleEvent) {
+ mFrame = aFrameToHandleEvent;
+ mPresShell = aFrameToHandleEvent->PresShell();
+ } else {
+ mFrame = nullptr;
+ mPresShell = nullptr;
+ }
+}
+
+void PresShell::EventHandler::EventTargetData::
+ SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent,
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aFrameToHandleEvent);
+ MOZ_ASSERT(aGUIEvent);
+
+ SetFrameAndComputePresShell(aFrameToHandleEvent);
+ SetContentForEventFromFrame(aGUIEvent);
+}
+
+void PresShell::EventHandler::EventTargetData::SetContentForEventFromFrame(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(mFrame);
+ mContent = nullptr;
+ mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(mContent));
+ AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent);
+}
+
+nsIContent* PresShell::EventHandler::EventTargetData::GetFrameContent() const {
+ return mFrame ? mFrame->GetContent() : nullptr;
+}
+
+void PresShell::EventHandler::EventTargetData::
+ AssertIfEventTargetContentAndFrameContentMismatch(
+ const WidgetGUIEvent* aGUIEvent) const {
+#ifdef DEBUG
+ if (!mContent || !mFrame || !mFrame->GetContent()) {
+ return;
+ }
+
+ // If we know the event, we can compute the target correctly.
+ if (aGUIEvent) {
+ nsCOMPtr<nsIContent> content;
+ mFrame->GetContentForEvent(aGUIEvent, getter_AddRefs(content));
+ MOZ_ASSERT(mContent == content);
+ return;
+ }
+
+ // Otherwise, we can check only whether mContent is an inclusive ancestor
+ // element or not.
+ if (!mContent->IsElement()) {
+ MOZ_ASSERT(mContent == mFrame->GetContent());
+ return;
+ }
+ const Element* const closestInclusiveAncestorElement =
+ [&]() -> const Element* {
+ for (const nsIContent* const content :
+ mFrame->GetContent()->InclusiveFlatTreeAncestorsOfType<nsIContent>()) {
+ if (content->IsElement()) {
+ return content->AsElement();
+ }
+ }
+ return nullptr;
+ }();
+ if (closestInclusiveAncestorElement == mContent) {
+ return;
+ }
+ if (closestInclusiveAncestorElement->IsInNativeAnonymousSubtree() &&
+ (mContent == closestInclusiveAncestorElement
+ ->FindFirstNonChromeOnlyAccessContent())) {
+ return;
+ }
+ NS_WARNING(nsPrintfCString("mContent=%s", ToString(*mContent).c_str()).get());
+ NS_WARNING(nsPrintfCString("mFrame->GetContent()=%s",
+ ToString(*mFrame->GetContent()).c_str())
+ .get());
+ MOZ_ASSERT(mContent == mFrame->GetContent());
+#endif // #ifdef DEBUG
+}
+
+bool PresShell::EventHandler::EventTargetData::MaybeRetargetToActiveDocument(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(mFrame);
+ MOZ_ASSERT(mPresShell);
+ MOZ_ASSERT(!mContent, "Doesn't support to retarget the content");
+
+ EventStateManager* activeESM =
+ EventStateManager::GetActiveEventStateManager();
+ if (!activeESM) {
+ return false;
+ }
+
+ if (aGUIEvent->mClass != ePointerEventClass &&
+ !aGUIEvent->HasMouseEventMessage()) {
+ return false;
+ }
+
+ if (activeESM == GetEventStateManager()) {
+ return false;
+ }
+
+ nsPresContext* activePresContext = activeESM->GetPresContext();
+ if (!activePresContext) {
+ return false;
+ }
+
+ PresShell* activePresShell = activePresContext->GetPresShell();
+ if (!activePresShell) {
+ return false;
+ }
+
+ // Note, currently for backwards compatibility we don't forward mouse events
+ // to the active document when mouse is over some subdocument.
+ if (!nsContentUtils::ContentIsCrossDocDescendantOf(
+ activePresShell->GetDocument(), GetDocument())) {
+ return false;
+ }
+
+ SetFrameAndComputePresShell(activePresShell->GetRootFrame());
+ return true;
+}
+
+bool PresShell::EventHandler::EventTargetData::ComputeElementFromFrame(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+ MOZ_ASSERT(aGUIEvent->IsUsingCoordinates());
+ MOZ_ASSERT(mPresShell);
+ MOZ_ASSERT(mFrame);
+
+ SetContentForEventFromFrame(aGUIEvent);
+
+ // If there is no content for this frame, target it anyway. Some frames can
+ // be targeted but do not have content, particularly windows with scrolling
+ // off.
+ if (!mContent) {
+ return true;
+ }
+
+ // Bug 103055, bug 185889: mouse events apply to *elements*, not all nodes.
+ // Thus we get the nearest element parent here.
+ // XXX we leave the frame the same even if we find an element parent, so that
+ // the text frame will receive the event (selection and friends are the ones
+ // who care about that anyway)
+ //
+ // We use weak pointers because during this tight loop, the node
+ // will *not* go away. And this happens on every mousemove.
+ nsIContent* content = mContent;
+ while (content && !content->IsElement()) {
+ content = content->GetFlattenedTreeParent();
+ }
+ mContent = content;
+
+ // If we found an element, target it. Otherwise, target *nothing*.
+ return !!mContent;
+}
+
+void PresShell::EventHandler::EventTargetData::UpdateWheelEventTarget(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+
+ if (aGUIEvent->mMessage != eWheel) {
+ return;
+ }
+
+ // If dom.event.wheel-event-groups.enabled is not set or the stored
+ // event target is removed, we will not get a event target frame from the
+ // wheel transaction here.
+ nsIFrame* groupFrame = WheelTransaction::GetEventTargetFrame();
+ if (!groupFrame) {
+ return;
+ }
+
+ // If the browsing context is no longer the same as the context of the
+ // current wheel transaction, do not override the event target.
+ if (!groupFrame->PresContext() || !groupFrame->PresShell() ||
+ groupFrame->PresContext() != GetPresContext()) {
+ return;
+ }
+
+ // If dom.event.wheel-event-groups.enabled is set and whe have a stored
+ // event target from the wheel transaction, override the event target.
+ SetFrameAndComputePresShellAndContent(groupFrame, aGUIEvent);
+}
+
+void PresShell::EventHandler::EventTargetData::UpdateTouchEventTarget(
+ WidgetGUIEvent* aGUIEvent) {
+ MOZ_ASSERT(aGUIEvent);
+
+ if (aGUIEvent->mClass != eTouchEventClass) {
+ return;
+ }
+
+ if (aGUIEvent->mMessage == eTouchStart) {
+ WidgetTouchEvent* touchEvent = aGUIEvent->AsTouchEvent();
+ nsIFrame* newFrame =
+ TouchManager::SuppressInvalidPointsAndGetTargetedFrame(touchEvent);
+ if (!newFrame) {
+ return; // XXX Why don't we stop handling the event in this case?
+ }
+ SetFrameAndComputePresShellAndContent(newFrame, aGUIEvent);
+ return;
+ }
+
+ PresShell* newPresShell = PresShell::GetShellForTouchEvent(aGUIEvent);
+ if (!newPresShell) {
+ return; // XXX Why don't we stop handling the event in this case?
+ }
+
+ // Touch events (except touchstart) are dispatching to the captured
+ // element. Get correct shell from it.
+ mPresShell = newPresShell;
+}
+
+/******************************************************************************
+ * PresShell::EventHandler::HandlingTimeAccumulator
+ ******************************************************************************/
+
+PresShell::EventHandler::HandlingTimeAccumulator::HandlingTimeAccumulator(
+ const PresShell::EventHandler& aEventHandler, const WidgetEvent* aEvent)
+ : mEventHandler(aEventHandler),
+ mEvent(aEvent),
+ mHandlingStartTime(TimeStamp::Now()) {
+ MOZ_ASSERT(mEvent);
+ MOZ_ASSERT(mEvent->IsTrusted());
+}
+
+PresShell::EventHandler::HandlingTimeAccumulator::~HandlingTimeAccumulator() {
+ if (mEvent->mTimeStamp <= mEventHandler.mPresShell->mLastOSWake) {
+ return;
+ }
+
+ switch (mEvent->mMessage) {
+ case eKeyPress:
+ case eKeyDown:
+ case eKeyUp:
+ Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_KEYBOARD_MS,
+ mHandlingStartTime);
+ return;
+ case eMouseDown:
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_HANDLED_MOUSE_DOWN_MS, mHandlingStartTime);
+ return;
+ case eMouseUp:
+ Telemetry::AccumulateTimeDelta(Telemetry::INPUT_EVENT_HANDLED_MOUSE_UP_MS,
+ mHandlingStartTime);
+ return;
+ case eMouseMove:
+ if (mEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_HANDLED_APZ_MOUSE_MOVE_MS,
+ mHandlingStartTime);
+ }
+ return;
+ case eWheel:
+ if (mEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_HANDLED_APZ_WHEEL_MS, mHandlingStartTime);
+ }
+ return;
+ case eTouchMove:
+ if (mEvent->mFlags.mHandledByAPZ) {
+ Telemetry::AccumulateTimeDelta(
+ Telemetry::INPUT_EVENT_HANDLED_APZ_TOUCH_MOVE_MS,
+ mHandlingStartTime);
+ }
+ return;
+ default:
+ return;
+ }
+}
+
+void PresShell::EndPaint() {
+ ClearPendingVisualScrollUpdate();
+
+ if (mDocument) {
+ mDocument->EnumerateSubDocuments([](Document& aSubDoc) {
+ if (PresShell* presShell = aSubDoc.GetPresShell()) {
+ presShell->EndPaint();
+ }
+ return CallState::Continue;
+ });
+
+ if (nsPresContext* presContext = GetPresContext()) {
+ if (PerformanceMainThread* perf =
+ presContext->GetPerformanceMainThread()) {
+ perf->FinalizeLCPEntriesForText();
+ }
+ }
+ }
+}
+
+void PresShell::PingPerTickTelemetry(FlushType aFlushType) {
+ mLayoutTelemetry.PingPerTickTelemetry(aFlushType);
+}
+
+bool PresShell::GetZoomableByAPZ() const {
+ return mZoomConstraintsClient && mZoomConstraintsClient->GetAllowZoom();
+}
+
+bool PresShell::ReflowForHiddenContentIfNeeded() {
+ if (mHiddenContentInForcedLayout.IsEmpty()) {
+ return false;
+ }
+ mDocument->FlushPendingNotifications(FlushType::Layout);
+ mHiddenContentInForcedLayout.Clear();
+ return true;
+}
+
+void PresShell::UpdateHiddenContentInForcedLayout(nsIFrame* aFrame) {
+ if (!aFrame || !aFrame->IsSubtreeDirty() ||
+ !StaticPrefs::layout_css_content_visibility_enabled()) {
+ return;
+ }
+
+ nsIFrame* topmostFrameWithContentHidden = nullptr;
+ for (nsIFrame* cur = aFrame->GetInFlowParent(); cur;
+ cur = cur->GetInFlowParent()) {
+ if (cur->HidesContent()) {
+ topmostFrameWithContentHidden = cur;
+ mHiddenContentInForcedLayout.Insert(cur->GetContent());
+ }
+ }
+
+ if (mHiddenContentInForcedLayout.IsEmpty()) {
+ return;
+ }
+
+ // Queue and immediately flush a reflow for this node.
+ MOZ_ASSERT(topmostFrameWithContentHidden);
+ FrameNeedsReflow(topmostFrameWithContentHidden, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+}
+
+void PresShell::EnsureReflowIfFrameHasHiddenContent(nsIFrame* aFrame) {
+ MOZ_ASSERT(mHiddenContentInForcedLayout.IsEmpty());
+
+ UpdateHiddenContentInForcedLayout(aFrame);
+ ReflowForHiddenContentIfNeeded();
+}
+
+bool PresShell::IsForcingLayoutForHiddenContent(const nsIFrame* aFrame) const {
+ return mHiddenContentInForcedLayout.Contains(aFrame->GetContent());
+}
+
+void PresShell::UpdateRelevancyOfContentVisibilityAutoFrames() {
+ if (mContentVisibilityRelevancyToUpdate.isEmpty()) {
+ return;
+ }
+
+ for (nsIFrame* frame : mContentVisibilityAutoFrames) {
+ frame->UpdateIsRelevantContent(mContentVisibilityRelevancyToUpdate);
+ }
+
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
+ }
+
+ mContentVisibilityRelevancyToUpdate.clear();
+}
+
+void PresShell::ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason) {
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return;
+ }
+
+ mContentVisibilityRelevancyToUpdate += aReason;
+
+ SetNeedLayoutFlush();
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->RefreshDriver()->EnsureContentRelevancyUpdateHappens();
+ }
+}
+
+PresShell::ProximityToViewportResult PresShell::DetermineProximityToViewport() {
+ ProximityToViewportResult result;
+ if (mContentVisibilityAutoFrames.IsEmpty()) {
+ return result;
+ }
+
+ auto margin = LengthPercentage::FromPercentage(
+ StaticPrefs::layout_css_content_visibility_relevant_content_margin() /
+ 100.0f);
+
+ auto rootMargin = StyleRect<LengthPercentage>::WithAllSides(margin);
+
+ auto input = DOMIntersectionObserver::ComputeInput(
+ *mDocument, /* aRoot = */ nullptr, &rootMargin);
+
+ for (nsIFrame* frame : mContentVisibilityAutoFrames) {
+ auto* element = frame->GetContent()->AsElement();
+ result.mAnyScrollIntoViewFlag |=
+ element->TemporarilyVisibleForScrolledIntoViewDescendant();
+
+ // 14.2.3.1
+ Maybe<bool> oldVisibility = element->GetVisibleForContentVisibility();
+ bool checkForInitialDetermination =
+ oldVisibility.isNothing() &&
+ (element->GetContentRelevancy().isNothing() ||
+ element->GetContentRelevancy()->isEmpty());
+
+ // 14.2.3.2
+ bool intersects =
+ DOMIntersectionObserver::Intersect(
+ input, *element,
+ DOMIntersectionObserver::IsForProximityToViewport::Yes)
+ .Intersects();
+ element->SetVisibleForContentVisibility(intersects);
+ if (oldVisibility.isNothing() || *oldVisibility != intersects) {
+ frame->UpdateIsRelevantContent(ContentRelevancyReason::Visible);
+ }
+
+ // 14.2.3.3
+ if (checkForInitialDetermination && intersects) {
+ result.mHadInitialDetermination = true;
+ }
+ }
+ if (nsPresContext* presContext = GetPresContext()) {
+ presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
+ }
+
+ return result;
+}
+
+void PresShell::ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags()
+ const {
+ for (nsIFrame* frame : mContentVisibilityAutoFrames) {
+ frame->GetContent()
+ ->AsElement()
+ ->SetTemporarilyVisibleForScrolledIntoViewDescendant(false);
+ }
+}
+
+void PresShell::UpdateContentRelevancyImmediately(
+ ContentRelevancyReason aReason) {
+ if (MOZ_UNLIKELY(mIsDestroying)) {
+ return;
+ }
+
+ mContentVisibilityRelevancyToUpdate += aReason;
+
+ SetNeedLayoutFlush();
+ UpdateRelevancyOfContentVisibilityAutoFrames();
+}
diff --git a/layout/base/PresShell.h b/layout/base/PresShell.h
new file mode 100644
index 0000000000..c6a965e81e
--- /dev/null
+++ b/layout/base/PresShell.h
@@ -0,0 +1,3287 @@
+/* -*- 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 presentation of a document, part 2 */
+
+#ifndef mozilla_PresShell_h
+#define mozilla_PresShell_h
+
+#include "DepthOrderedFrameList.h"
+#include "mozilla/PresShellForwards.h"
+
+#include <stdio.h> // for FILE definition
+#include "FrameMetrics.h"
+#include "LayoutConstants.h"
+#include "TouchManager.h"
+#include "Units.h"
+#include "Visibility.h"
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/FlushType.h"
+#include "mozilla/Logging.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ScrollTypes.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/dom/DocumentBinding.h"
+#include "mozilla/layers/FocusTarget.h"
+#include "mozilla/layout/LayoutTelemetryTools.h"
+#include "mozilla/widget/ThemeChangeKind.h"
+#include "nsColor.h"
+#include "nsCOMArray.h"
+#include "nsCoord.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsFrameManager.h"
+#include "nsFrameState.h"
+#include "nsIContent.h"
+#include "nsIObserver.h"
+#include "nsISelectionController.h"
+#include "nsQueryFrame.h"
+#include "nsPresArena.h"
+#include "nsPresContext.h"
+#include "nsRect.h"
+#include "nsRefreshObservers.h"
+#include "nsStringFwd.h"
+#include "nsStubDocumentObserver.h"
+#include "nsTHashSet.h"
+#include "nsThreadUtils.h"
+#include "nsWeakReference.h"
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+class AutoPointerEventTargetUpdater;
+class AutoWeakFrame;
+class gfxContext;
+class MobileViewportManager;
+class nsAutoCauseReflowNotifier;
+class nsCanvasFrame;
+class nsCaret;
+class nsCSSFrameConstructor;
+class nsDocShell;
+class nsFrameSelection;
+class nsIDocShell;
+class nsIFrame;
+class nsILayoutHistoryState;
+class nsINode;
+class nsPageSequenceFrame;
+class nsIReflowCallback;
+class nsIScrollableFrame;
+class nsITimer;
+class nsPIDOMWindowOuter;
+class nsPresShellEventCB;
+class nsRange;
+class nsRefreshDriver;
+class nsRegion;
+class nsView;
+class nsViewManager;
+class nsWindowSizes;
+struct RangePaintInfo;
+#ifdef MOZ_REFLOW_PERF
+class ReflowCountMgr;
+#endif
+class WeakFrame;
+class nsTextFrame;
+class ZoomConstraintsClient;
+
+struct nsCallbackEventRequest;
+
+namespace mozilla {
+class nsDisplayList;
+class nsDisplayListBuilder;
+class FallbackRenderer;
+
+class AccessibleCaretEventHub;
+class GeckoMVMContext;
+class OverflowChangedTracker;
+class StyleSheet;
+
+class ProfileChunkedBuffer;
+
+#ifdef ACCESSIBILITY
+namespace a11y {
+class DocAccessible;
+} // namespace a11y
+#endif
+
+namespace dom {
+class BrowserParent;
+class Element;
+class Event;
+class HTMLSlotElement;
+class Selection;
+class PerformanceMainThread;
+} // namespace dom
+
+namespace gfx {
+class SourceSurface;
+} // namespace gfx
+
+namespace layers {
+class LayerManager;
+struct LayersId;
+} // namespace layers
+
+namespace layout {
+class ScrollAnchorContainer;
+} // namespace layout
+
+// 039d8ffc-fa55-42d7-a53a-388cb129b052
+#define NS_PRESSHELL_IID \
+ { \
+ 0x039d8ffc, 0xfa55, 0x42d7, { \
+ 0xa5, 0x3a, 0x38, 0x8c, 0xb1, 0x29, 0xb0, 0x52 \
+ } \
+ }
+
+#undef NOISY_INTERRUPTIBLE_REFLOW
+
+/**
+ * Presentation shell. Presentation shells are the controlling point for
+ * managing the presentation of a document. The presentation shell holds a
+ * live reference to the document, the presentation context, the style
+ * manager, the style set and the root frame.
+ *
+ * When this object is Release'd, it will release the document, the
+ * presentation context, the style manager, the style set and the root frame.
+ */
+
+class PresShell final : public nsStubDocumentObserver,
+ public nsISelectionController,
+ public nsIObserver,
+ public nsSupportsWeakReference {
+ typedef dom::Document Document;
+ typedef dom::Element Element;
+ typedef gfx::SourceSurface SourceSurface;
+ typedef layers::FocusTarget FocusTarget;
+ typedef layers::FrameMetrics FrameMetrics;
+ typedef layers::LayerManager LayerManager;
+
+ // A set type for tracking visible frames, for use by the visibility code in
+ // PresShell. The set contains nsIFrame* pointers.
+ typedef nsTHashSet<nsIFrame*> VisibleFrames;
+
+ public:
+ explicit PresShell(Document* aDocument);
+
+ // nsISupports
+ NS_DECL_ISUPPORTS
+
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRESSHELL_IID)
+
+ static bool AccessibleCaretEnabled(nsIDocShell* aDocShell);
+
+ /**
+ * Return the active content currently capturing the mouse if any.
+ */
+ static nsIContent* GetCapturingContent() {
+ return sCapturingContentInfo.mContent;
+ }
+
+ /**
+ */
+ static dom::BrowserParent* GetCapturingRemoteTarget() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return sCapturingContentInfo.mRemoteTarget;
+ }
+
+ /**
+ * Allow or disallow mouse capturing.
+ */
+ static void AllowMouseCapture(bool aAllowed) {
+ sCapturingContentInfo.mAllowed = aAllowed;
+ }
+
+ /**
+ * Returns true if there is an active mouse capture that wants to prevent
+ * drags.
+ */
+ static bool IsMouseCapturePreventingDrag() {
+ return sCapturingContentInfo.mPreventDrag && sCapturingContentInfo.mContent;
+ }
+
+ static void ClearMouseCaptureOnView(nsView* aView);
+
+ // Clear the capture content if it exists in this process.
+ static void ClearMouseCapture();
+
+ // If a frame in the subtree rooted at aFrame is capturing the mouse then
+ // clears that capture.
+ //
+ // NOTE(emilio): This is needed only so that mouse events captured by a remote
+ // frame don't remain being captured by the frame while hidden, see
+ // dom/events/test/browser_mouse_enterleave_switch_tab.js, which is the only
+ // test that meaningfully exercises this code path.
+ //
+ // We could consider maybe removing this, since the capturing content gets
+ // reset on mouse/pointerdown? Or maybe exposing an API so that the front-end
+ // does this.
+ static void ClearMouseCapture(nsIFrame* aFrame);
+
+#ifdef ACCESSIBILITY
+ /**
+ * Return the document accessible for this PresShell if there is one.
+ */
+ a11y::DocAccessible* GetDocAccessible() const { return mDocAccessible; }
+
+ /**
+ * Set the document accessible for this PresShell.
+ */
+ void SetDocAccessible(a11y::DocAccessible* aDocAccessible) {
+ mDocAccessible = aDocAccessible;
+ }
+#endif // #ifdef ACCESSIBILITY
+
+ /**
+ * See `mLastOverWindowPointerLocation`.
+ */
+ const nsPoint& GetLastOverWindowPointerLocation() const {
+ return mLastOverWindowPointerLocation;
+ }
+
+ MOZ_CAN_RUN_SCRIPT void Init(nsPresContext*, nsViewManager*);
+
+ /**
+ * All callers are responsible for calling |Destroy| after calling
+ * |EndObservingDocument|. It needs to be separate only because form
+ * controls incorrectly store their data in the frames rather than the
+ * content model and printing calls |EndObservingDocument| multiple
+ * times to make form controls behave nicely when printed.
+ */
+ void Destroy();
+
+ bool IsDestroying() { return mIsDestroying; }
+
+ /**
+ * All frames owned by the shell are allocated from an arena. They
+ * are also recycled using free lists. Separate free lists are
+ * maintained for each frame type (aID), which must always correspond
+ * to the same aSize value. AllocateFrame is infallible and will abort
+ * on out-of-memory.
+ */
+ void* AllocateFrame(nsQueryFrame::FrameIID aID, size_t aSize) {
+#define FRAME_ID(classname, ...) \
+ static_assert(size_t(nsQueryFrame::FrameIID::classname##_id) == \
+ size_t(eArenaObjectID_##classname), \
+ "");
+#define ABSTRACT_FRAME_ID(classname) \
+ static_assert(size_t(nsQueryFrame::FrameIID::classname##_id) == \
+ size_t(eArenaObjectID_##classname), \
+ "");
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+ return AllocateByObjectID(ArenaObjectID(size_t(aID)), aSize);
+ }
+
+ void FreeFrame(nsQueryFrame::FrameIID aID, void* aPtr) {
+ return FreeByObjectID(ArenaObjectID(size_t(aID)), aPtr);
+ }
+
+ void* AllocateByObjectID(ArenaObjectID aID, size_t aSize) {
+ void* result = mFrameArena.Allocate(aID, aSize);
+ RecordAlloc(result);
+ return result;
+ }
+
+ void FreeByObjectID(ArenaObjectID aID, void* aPtr) {
+ RecordFree(aPtr);
+ if (!mIsDestroying) {
+ mFrameArena.Free(aID, aPtr);
+ }
+ }
+
+ Document* GetDocument() const { return mDocument; }
+
+ nsPresContext* GetPresContext() const { return mPresContext; }
+
+ nsViewManager* GetViewManager() const { return mViewManager; }
+
+ nsRefreshDriver* GetRefreshDriver() const;
+
+ nsCSSFrameConstructor* FrameConstructor() const {
+ return mFrameConstructor.get();
+ }
+
+ /**
+ * FrameSelection will return the Frame based selection API.
+ * You cannot go back and forth anymore with QI between nsIDOM sel and
+ * nsIFrame sel.
+ */
+ already_AddRefed<nsFrameSelection> FrameSelection();
+
+ /**
+ * ConstFrameSelection returns an object which methods are safe to use for
+ * example in nsIFrame code.
+ */
+ const nsFrameSelection* ConstFrameSelection() const { return mSelection; }
+
+ // Start receiving notifications from our document. If called after Destroy,
+ // this will be ignored.
+ void BeginObservingDocument();
+
+ // Stop receiving notifications from our document. If called after Destroy,
+ // this will be ignored.
+ void EndObservingDocument();
+
+ bool IsObservingDocument() const { return mIsObservingDocument; }
+
+ /**
+ * Return whether Initialize() was previously called.
+ */
+ bool DidInitialize() const { return mDidInitialize; }
+
+ /**
+ * Perform initialization. Constructs the frame for the root content
+ * object and then enqueues a reflow of the frame model.
+ *
+ * Callers of this method must hold a reference to this shell that
+ * is guaranteed to survive through arbitrary script execution.
+ * Calling Initialize can execute arbitrary script.
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY nsresult Initialize();
+
+ /**
+ * Schedule a reflow for the frame model into a new width and height. The
+ * coordinates for aWidth and aHeight must be in standard nscoord's.
+ *
+ * Returns whether layout might have changed.
+ */
+ MOZ_CAN_RUN_SCRIPT void ResizeReflow(
+ nscoord aWidth, nscoord aHeight,
+ ResizeReflowOptions = ResizeReflowOptions::NoOption);
+ MOZ_CAN_RUN_SCRIPT bool ResizeReflowIgnoreOverride(
+ nscoord aWidth, nscoord aHeight,
+ ResizeReflowOptions = ResizeReflowOptions::NoOption);
+ MOZ_CAN_RUN_SCRIPT void ForceResizeReflowWithCurrentDimensions();
+
+ /**
+ * Add this pres shell to the refresh driver to be observed for resize
+ * event if applicable.
+ */
+ void AddResizeEventFlushObserverIfNeeded();
+
+ /**
+ * Returns true if the document hosted by this presShell is in a devtools
+ * Responsive Design Mode browsing context.
+ */
+ bool InRDMPane();
+
+#if defined(MOZ_WIDGET_ANDROID)
+ /**
+ * If the dynamic toolbar is not expanded, notify the app to do so.
+ */
+ void MaybeNotifyShowDynamicToolbar();
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+ void RefreshZoomConstraintsForScreenSizeChange();
+
+ private:
+ /**
+ * This is what ResizeReflowIgnoreOverride does when not shrink-wrapping (that
+ * is, when ResizeReflowOptions::BSizeLimit is not specified).
+ */
+ bool SimpleResizeReflow(nscoord aWidth, nscoord aHeight);
+
+ bool CanHandleUserInputEvents(WidgetGUIEvent* aGUIEvent);
+
+ public:
+ /**
+ * Updates pending layout, assuming reasonable (up-to-date, or mid-update for
+ * container queries) styling of the page. Returns whether a reflow did not
+ * get interrupted (and thus layout should be considered fully up-to-date).
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool DoFlushLayout(bool aInterruptible);
+
+ /**
+ * Note that the assumptions that determine whether we need a mobile viewport
+ * manager may have changed.
+ */
+ MOZ_CAN_RUN_SCRIPT void MaybeRecreateMobileViewportManager(
+ bool aAfterInitialization);
+
+ /**
+ * Returns true if this document uses mobile viewport sizing (including
+ * processing of <meta name="viewport"> tags).
+ *
+ * Note that having a MobileViewportManager does not necessarily mean using
+ * mobile viewport sizing, as with desktop zooming we can have a
+ * MobileViewportManager on desktop, but we only want to do mobile viewport
+ * sizing on mobile. (TODO: Rename MobileViewportManager to reflect its more
+ * general role.)
+ */
+ bool UsesMobileViewportSizing() const;
+
+ /**
+ * Get the MobileViewportManager used to manage the document's mobile
+ * viewport. Will return null in situations where we don't have a mobile
+ * viewport, and for documents that are not the root content document.
+ */
+ RefPtr<MobileViewportManager> GetMobileViewportManager() const;
+
+ /**
+ * Return true if the presshell expects layout flush.
+ */
+ bool IsLayoutFlushObserver();
+
+ /**
+ * Called when document load completes.
+ */
+ void LoadComplete();
+ /**
+ * This calls through to the frame manager to get the root frame.
+ */
+ nsIFrame* GetRootFrame() const { return mFrameManager->GetRootFrame(); }
+
+ /*
+ * Get root scroll frame from FrameManager()->GetRootFrame().
+ */
+ nsIFrame* GetRootScrollFrame() const;
+
+ /*
+ * The same as GetRootScrollFrame, but returns an nsIScrollableFrame
+ */
+ nsIScrollableFrame* GetRootScrollFrameAsScrollable() const;
+
+ /**
+ * Get the current focused content or DOM selection that should be the
+ * target for scrolling.
+ */
+ already_AddRefed<nsIContent> GetContentForScrolling() const;
+
+ /**
+ * Get the DOM selection that should be the target for scrolling, if there
+ * is no focused content.
+ */
+ already_AddRefed<nsIContent> GetSelectedContentForScrolling() const;
+
+ /**
+ * Gets nearest scrollable frame from the specified content node. The frame
+ * is scrollable with overflow:scroll or overflow:auto in some direction when
+ * aDirection is eEither. Otherwise, this returns a nearest frame that is
+ * scrollable in the specified direction.
+ */
+ nsIScrollableFrame* GetScrollableFrameToScrollForContent(
+ nsIContent* aContent, layers::ScrollDirections aDirections);
+
+ /**
+ * Gets nearest scrollable frame from current focused content or DOM
+ * selection if there is no focused content. The frame is scrollable with
+ * overflow:scroll or overflow:auto in some direction when aDirection is
+ * eEither. Otherwise, this returns a nearest frame that is scrollable in
+ * the specified direction.
+ */
+ nsIScrollableFrame* GetScrollableFrameToScroll(
+ layers::ScrollDirections aDirections);
+
+ /**
+ * Returns the page sequence frame associated with the frame hierarchy.
+ * Returns nullptr if not a paginated view.
+ */
+ nsPageSequenceFrame* GetPageSequenceFrame() const;
+
+ /**
+ * Returns the canvas frame associated with the frame hierarchy.
+ * Returns nullptr if is XUL document.
+ */
+ nsCanvasFrame* GetCanvasFrame() const;
+
+ void PostPendingScrollAnchorSelection(
+ layout::ScrollAnchorContainer* aContainer);
+ void FlushPendingScrollAnchorSelections();
+ void PostPendingScrollAnchorAdjustment(
+ layout::ScrollAnchorContainer* aContainer);
+
+ void PostPendingScrollResnap(nsIScrollableFrame* aScrollableFrame);
+ void FlushPendingScrollResnap();
+
+ void CancelAllPendingReflows();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void NotifyCounterStylesAreDirty();
+
+ bool FrameIsAncestorOfDirtyRoot(nsIFrame* aFrame) const;
+
+ /**
+ * Destroy the frames for aElement, and reconstruct them asynchronously if
+ * needed.
+ *
+ * Note that this may destroy frames for an arbitrary ancestor, depending on
+ * the frame tree structure.
+ */
+ void DestroyFramesForAndRestyle(Element* aElement);
+
+ /**
+ * Called when a ShadowRoot will be attached to an element (and thus the flat
+ * tree children will go away).
+ */
+ void ShadowRootWillBeAttached(Element& aElement);
+
+ /**
+ * Handles all the layout stuff needed when the slot assignment for an element
+ * is about to change.
+ *
+ * Only called when the slot attribute of the element changes, the rest of
+ * the changes should be handled in ShadowRoot.
+ */
+ void SlotAssignmentWillChange(Element& aElement,
+ dom::HTMLSlotElement* aOldSlot,
+ dom::HTMLSlotElement* aNewSlot);
+
+ void PostRecreateFramesFor(Element*);
+ void RestyleForAnimation(Element*, RestyleHint);
+
+ /**
+ * Determine if it is safe to flush all pending notifications.
+ */
+ bool IsSafeToFlush() const;
+
+ /**
+ * Informs the document's FontFaceSet that the refresh driver ticked,
+ * flushing style and layout.
+ */
+ void NotifyFontFaceSetOnRefresh();
+
+ // Removes ourself from the list of layout / style / and resize refresh driver
+ // observers.
+ //
+ // Right now this is only used for documents in the BFCache, so if you want to
+ // use this for anything else you need to ensure we don't end up in those
+ // lists after calling this, but before calling StartObservingRefreshDriver
+ // again.
+ //
+ // That is handled by the mDocument->GetBFCacheEntry checks in
+ // DoObserve*Flushes functions, though that could conceivably become a boolean
+ // member in the shell if needed.
+ //
+ // Callers are responsible of manually calling StartObservingRefreshDriver
+ // again.
+ void StopObservingRefreshDriver();
+ void StartObservingRefreshDriver();
+
+ bool ObservingStyleFlushes() const { return mObservingStyleFlushes; }
+ bool ObservingLayoutFlushes() const { return mObservingLayoutFlushes; }
+
+ void ObserveStyleFlushes() {
+ if (!ObservingStyleFlushes()) {
+ DoObserveStyleFlushes();
+ }
+ }
+
+ /**
+ * Callbacks will be called even if reflow itself fails for
+ * some reason.
+ */
+ nsresult PostReflowCallback(nsIReflowCallback* aCallback);
+ void CancelReflowCallback(nsIReflowCallback* aCallback);
+
+ void ScheduleBeforeFirstPaint();
+ void UnsuppressAndInvalidate();
+
+ void ClearFrameRefs(nsIFrame* aFrame);
+
+ // Clears the selection of the older focused frame selection if any.
+ void FrameSelectionWillTakeFocus(nsFrameSelection&);
+
+ // Clears and repaint mFocusedFrameSelection if it matches the argument.
+ void FrameSelectionWillLoseFocus(nsFrameSelection&);
+
+ /**
+ * Get a reference rendering context. This is a context that should not
+ * be rendered to, but is suitable for measuring text and performing
+ * other non-rendering operations. Guaranteed to return non-null.
+ */
+ mozilla::UniquePtr<gfxContext> CreateReferenceRenderingContext();
+
+ /**
+ * Scrolls the view of the document so that the given area of a frame
+ * is visible, if possible. Layout is not flushed before scrolling.
+ *
+ * @param aRect Relative to aTargetFrame. If none, the bounding box of
+ * aTargetFrame will be used. The rect edges will be respected even if the
+ * rect is empty.
+ * @param aVertical see ScrollContentIntoView and ScrollAxis
+ * @param aHorizontal see ScrollContentIntoView and ScrollAxis
+ * @param aScrollFlags if ScrollFirstAncestorOnly is set, only the
+ * nearest scrollable ancestor is scrolled, otherwise all
+ * scrollable ancestors may be scrolled if necessary
+ * if ScrollOverflowHidden is set then we may scroll in a direction
+ * even if overflow:hidden is specified in that direction; otherwise
+ * we will not scroll in that direction when overflow:hidden is
+ * set for that direction
+ * If ScrollNoParentFrames is set then we only scroll
+ * nodes in this document, not in any parent documents which
+ * contain this document in a iframe or the like.
+ * @return true if any scrolling happened, false if no scrolling happened
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool ScrollFrameIntoView(nsIFrame* aTargetFrame,
+ const Maybe<nsRect>& aKnownRectRelativeToTarget,
+ ScrollAxis aVertical, ScrollAxis aHorizontal,
+ ScrollFlags aScrollFlags);
+
+ /**
+ * Suppress notification of the frame manager that frames are
+ * being destroyed.
+ */
+ void SetIgnoreFrameDestruction(bool aIgnore);
+
+ /**
+ * Get the AccessibleCaretEventHub, if it exists. AddRefs it.
+ */
+ already_AddRefed<AccessibleCaretEventHub> GetAccessibleCaretEventHub() const;
+
+ /**
+ * Get the caret, if it exists. AddRefs it.
+ */
+ already_AddRefed<nsCaret> GetCaret() const;
+
+ /**
+ * Set the current caret to a new caret. To undo this, call RestoreCaret.
+ */
+ void SetCaret(nsCaret* aNewCaret);
+
+ /**
+ * Restore the caret to the original caret that this pres shell was created
+ * with.
+ */
+ void RestoreCaret();
+
+ dom::Selection* GetCurrentSelection(SelectionType aSelectionType);
+
+ /**
+ * Gets the last selection that took focus in this document. This is basically
+ * the frame selection that's visible to the user.
+ */
+ nsFrameSelection* GetLastFocusedFrameSelection();
+
+ /**
+ * Interface to dispatch events via the presshell
+ * @note The caller must have a strong reference to the PresShell.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventWithTarget(WidgetEvent* aEvent, nsIFrame* aFrame,
+ nsIContent* aContent,
+ nsEventStatus* aEventStatus,
+ bool aIsHandlingNativeEvent = false,
+ nsIContent** aTargetContent = nullptr,
+ nsIContent* aOverrideClickTarget = nullptr) {
+ MOZ_ASSERT(aEvent);
+ EventHandler eventHandler(*this);
+ return eventHandler.HandleEventWithTarget(
+ aEvent, aFrame, aContent, aEventStatus, aIsHandlingNativeEvent,
+ aTargetContent, aOverrideClickTarget);
+ }
+
+ /**
+ * Dispatch event to content only (NOT full processing)
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ WidgetEvent* aEvent,
+ nsEventStatus* aStatus);
+
+ /**
+ * Dispatch event to content only (NOT full processing)
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleDOMEventWithTarget(nsIContent* aTargetContent,
+ dom::Event* aEvent, nsEventStatus* aStatus);
+
+ /**
+ * Return whether or not the event is valid to be dispatched
+ */
+ bool CanDispatchEvent(const WidgetGUIEvent* aEvent = nullptr) const;
+
+ /**
+ * Gets the current target event frame from the PresShell
+ */
+ nsIFrame* GetCurrentEventFrame();
+
+ /**
+ * Gets the current target event frame from the PresShell
+ */
+ already_AddRefed<nsIContent> GetEventTargetContent(WidgetEvent* aEvent);
+
+ /**
+ * Get and set the history state for the current document
+ */
+ nsresult CaptureHistoryState(nsILayoutHistoryState** aLayoutHistoryState);
+
+ /**
+ * Determine if reflow is currently locked
+ * returns true if reflow is locked, false otherwise
+ */
+ bool IsReflowLocked() const { return mIsReflowing; }
+
+ /**
+ * Called to find out if painting is suppressed for this presshell. If it is
+ * suppressd, we don't allow the painting of any layer but the background, and
+ * we don't recur into our children.
+ */
+ bool IsPaintingSuppressed() const { return mPaintingSuppressed; }
+
+ void TryUnsuppressPaintingSoon();
+
+ void UnsuppressPainting();
+ void InitPaintSuppressionTimer();
+ void CancelPaintSuppressionTimer();
+
+ /**
+ * Reconstruct frames for all elements in the document
+ */
+ MOZ_CAN_RUN_SCRIPT void ReconstructFrames();
+
+ /**
+ * See if reflow verification is enabled. To enable reflow verification add
+ * "verifyreflow:1" to your MOZ_LOG environment variable (any non-zero
+ * debug level will work). Or, call SetVerifyReflowEnable with true.
+ */
+ static bool GetVerifyReflowEnable();
+
+ /**
+ * Set the verify-reflow enable flag.
+ */
+ static void SetVerifyReflowEnable(bool aEnabled);
+
+ nsIFrame* GetAbsoluteContainingBlock(nsIFrame* aFrame);
+
+#ifdef MOZ_REFLOW_PERF
+ void DumpReflows();
+ void CountReflows(const char* aName, nsIFrame* aFrame);
+ void PaintCount(const char* aName, gfxContext* aRenderingContext,
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ const nsPoint& aOffset, uint32_t aColor);
+ void SetPaintFrameCount(bool aOn);
+ bool IsPaintingFrameCounts();
+#endif // #ifdef MOZ_REFLOW_PERF
+
+ // Debugging hooks
+#ifdef DEBUG
+ void ListComputedStyles(FILE* out, int32_t aIndent = 0);
+#endif
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+ void ListStyleSheets(FILE* out, int32_t aIndent = 0);
+#endif
+
+ /**
+ * Stop all refresh drivers and carets in this presentation and
+ * in the presentations of subdocuments. Resets painting to a suppressed
+ * state.
+ * XXX this should include image animations
+ */
+ void Freeze(bool aIncludeSubDocuments = true);
+ bool IsFrozen() { return mFrozen; }
+
+ /**
+ * Restarts refresh drivers in this presentation and in the
+ * presentations of subdocuments, then do a full invalidate of the content
+ * area.
+ */
+ void Thaw(bool aIncludeSubDocuments = true);
+
+ void FireOrClearDelayedEvents(bool aFireEvents);
+
+ /**
+ * When this shell is disconnected from its containing docshell, we
+ * lose our container pointer. However, we'd still like to be able to target
+ * user events at the docshell's parent. This pointer allows us to do that.
+ * It should not be used for any other purpose.
+ */
+ void SetForwardingContainer(const WeakPtr<nsDocShell>& aContainer);
+
+ /**
+ * Render the document into an arbitrary gfxContext
+ * Designed for getting a picture of a document or a piece of a document
+ * Note that callers will generally want to call FlushPendingNotifications
+ * to get an up-to-date view of the document
+ * @param aRect is the region to capture into the offscreen buffer, in the
+ * root frame's coordinate system (if aIgnoreViewportScrolling is false)
+ * or in the root scrolled frame's coordinate system
+ * (if aIgnoreViewportScrolling is true). The coordinates are in appunits.
+ * @param aFlags see below;
+ * set RenderDocumentFlags::IsUntrusted if the contents may be passed to
+ * malicious agents. E.g. we might choose not to paint the contents of
+ * sensitive widgets such as the file name in a file upload widget, and we
+ * might choose not to paint themes.
+ * set RenderDocumentFlags::IgnoreViewportScrolling to ignore clipping and
+ * scrollbar painting due to scrolling in the viewport
+ * set RenderDocumentFlags::ResetViewportScrolling to temporarily set the
+ * viewport scroll position to 0 so that position:fixed elements are drawn
+ * at their initial position.
+ * set RenderDocumentFlags::DrawCaret to draw the caret if one would be
+ * visible (by default the caret is never drawn)
+ * set RenderDocumentFlags::UseWidgetLayers to force rendering to go
+ * through the layer manager for the window. This may be unexpectedly slow
+ * (if the layer manager must read back data from the GPU) or low-quality
+ * (if the layer manager reads back pixel data and scales it
+ * instead of rendering using the appropriate scaling). It may also
+ * slow everything down if the area rendered does not correspond to the
+ * normal visible area of the window.
+ * set RenderDocumentFlags::AsyncDecodeImages to avoid having images
+ * synchronously decoded during rendering.
+ * (by default images decode synchronously with RenderDocument)
+ * set RenderDocumentFlags::DocumentRelative to render the document as if
+ * there has been no scrolling and interpret |aRect| relative to the document
+ * instead of the CSS viewport. Only considered if
+ * RenderDocumentFlags::IgnoreViewportScrolling is set or the document is in
+ * ignore viewport scrolling mode
+ * (PresShell::SetIgnoreViewportScrolling/IgnoringViewportScrolling).
+ * set RenderDocumentFlags::UseHighQualityScaling to enable downscale on
+ * decode for images.
+ * @param aBackgroundColor a background color to render onto
+ * @param aRenderedContext the gfxContext to render to. We render so that
+ * one CSS pixel in the source document is rendered to one unit in the current
+ * transform.
+ */
+ nsresult RenderDocument(const nsRect& aRect, RenderDocumentFlags aFlags,
+ nscolor aBackgroundColor,
+ gfxContext* aRenderedContext);
+
+ /**
+ * Renders a node aNode to a surface and returns it. The aRegion may be used
+ * to clip the rendering. This region is measured in CSS pixels from the
+ * edge of the presshell area. The aPoint, aScreenRect and aFlags arguments
+ * function in a similar manner as RenderSelection.
+ */
+ already_AddRefed<SourceSurface> RenderNode(nsINode* aNode,
+ const Maybe<CSSIntRegion>& aRegion,
+ const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect,
+ RenderImageFlags aFlags);
+
+ /**
+ * Renders a selection to a surface and returns it. This method is primarily
+ * intended to create the drag feedback when dragging a selection.
+ *
+ * aScreenRect will be filled in with the bounding rectangle of the
+ * selection area on screen.
+ *
+ * If the area of the selection is large and the RenderImageFlags::AutoScale
+ * is set, the image will be scaled down. The argument aPoint is used in this
+ * case as a reference point when determining the new screen rectangle after
+ * scaling. Typically, this will be the mouse position, so that the screen
+ * rectangle is positioned such that the mouse is over the same point in the
+ * scaled image as in the original. When scaling does not occur, the mouse
+ * point isn't used because the position can be determined from the displayed
+ * frames.
+ */
+ already_AddRefed<SourceSurface> RenderSelection(
+ dom::Selection* aSelection, const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags);
+
+ void AddAutoWeakFrame(AutoWeakFrame* aWeakFrame);
+ void AddWeakFrame(WeakFrame* aWeakFrame);
+
+ void RemoveAutoWeakFrame(AutoWeakFrame* aWeakFrame);
+ void RemoveWeakFrame(WeakFrame* aWeakFrame);
+
+ /**
+ * Stop or restart non synthetic test mouse event handling on *all*
+ * presShells.
+ *
+ * @param aDisable If true, disable all non synthetic test mouse
+ * events on all presShells. Otherwise, enable them.
+ */
+ void DisableNonTestMouseEvents(bool aDisable);
+
+ /**
+ * Record the background color of the most recently drawn canvas. This color
+ * is composited on top of the user's default background color and then used
+ * to draw the background color of the canvas. See PresShell::Paint,
+ * PresShell::PaintDefaultBackground, and nsDocShell::SetupNewViewer;
+ * bug 488242, bug 476557 and other bugs mentioned there.
+ */
+ void SetCanvasBackground(nscolor aColor) {
+ mCanvasBackground.mViewportColor = aColor;
+ }
+ nscolor GetCanvasBackground() const {
+ return mCanvasBackground.mViewportColor;
+ }
+
+ struct CanvasBackground {
+ // The canvas frame background for the whole viewport.
+ nscolor mViewportColor = 0;
+ // The canvas frame background for a printed page. Note that when
+ // print-previewing / in paged mode we have multiple canvas frames (one for
+ // the viewport, one for each page).
+ nscolor mPageColor = 0;
+ bool mCSSSpecified = false;
+ };
+
+ // Use the current frame tree (if it exists) to update the background color of
+ // the canvas frames.
+ CanvasBackground ComputeCanvasBackground() const;
+ void UpdateCanvasBackground();
+
+ /**
+ * Computes the backstop color for the view: transparent if in a transparent
+ * widget, otherwise the PresContext default background color. This color is
+ * only visible if the contents of the view as a whole are translucent.
+ */
+ nscolor ComputeBackstopColor(nsView* aDisplayRoot);
+
+ void ObserveNativeAnonMutationsForPrint(bool aObserve) {
+ mObservesMutationsForPrint = aObserve;
+ }
+ bool ObservesNativeAnonMutationsForPrint() {
+ return mObservesMutationsForPrint;
+ }
+
+ void ActivenessMaybeChanged();
+ bool IsActive() const { return mIsActive; }
+
+ /**
+ * Keep track of how many times this presshell has been rendered to
+ * a window.
+ */
+ uint64_t GetPaintCount() { return mPaintCount; }
+ void IncrementPaintCount() { ++mPaintCount; }
+
+ /**
+ * Get the root DOM window of this presShell.
+ */
+ already_AddRefed<nsPIDOMWindowOuter> GetRootWindow();
+
+ /**
+ * This returns the focused DOM window under our top level window.
+ * I.e., when we are deactive, this returns the *last* focused DOM window.
+ */
+ already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow();
+
+ /**
+ * Get the focused content under this window.
+ */
+ already_AddRefed<nsIContent> GetFocusedContentInOurWindow() const;
+
+ /**
+ * Get the window renderer for the widget of the root view, if it has
+ * one.
+ */
+ WindowRenderer* GetWindowRenderer();
+
+ /**
+ * Return true iff there is a widget rendering this presShell and that
+ * widget is APZ-enabled.
+ */
+ bool AsyncPanZoomEnabled();
+
+ /**
+ * Track whether we're ignoring viewport scrolling for the purposes
+ * of painting. If we are ignoring, then layers aren't clipped to
+ * the CSS viewport and scrollbars aren't drawn.
+ */
+ bool IgnoringViewportScrolling() const {
+ return !!(mRenderingStateFlags &
+ RenderingStateFlags::IgnoringViewportScrolling);
+ }
+
+ float GetResolution() const { return mResolution.valueOr(1.0); }
+ float GetCumulativeResolution() const;
+
+ /**
+ * Accessors for a flag that tracks whether the most recent change to
+ * the pres shell's resolution was originated by the main thread.
+ */
+ bool IsResolutionUpdated() const { return mResolutionUpdated; }
+ void SetResolutionUpdated(bool aUpdated) { mResolutionUpdated = aUpdated; }
+
+ /**
+ * Returns true if the resolution has ever been changed by APZ.
+ */
+ bool IsResolutionUpdatedByApz() const { return mResolutionUpdatedByApz; }
+
+ /**
+ * Used by session restore code to restore a resolution before the first
+ * paint.
+ */
+ void SetRestoreResolution(float aResolution,
+ LayoutDeviceIntSize aDisplaySize);
+
+ /**
+ * Returns whether we are in a DrawWindow() call that used the
+ * DRAWWINDOW_DO_NOT_FLUSH flag.
+ */
+ bool InDrawWindowNotFlushing() const {
+ return !!(mRenderingStateFlags &
+ RenderingStateFlags::DrawWindowNotFlushing);
+ }
+
+ /**
+ * Set the isFirstPaint flag.
+ */
+ void SetIsFirstPaint(bool aIsFirstPaint) { mIsFirstPaint = aIsFirstPaint; }
+
+ /**
+ * Get the isFirstPaint flag.
+ */
+ bool GetIsFirstPaint() const { return mIsFirstPaint; }
+
+ uint32_t GetPresShellId() { return mPresShellId; }
+
+ /**
+ * Dispatch a mouse move event based on the most recent mouse position if
+ * this PresShell is visible. This is used when the contents of the page
+ * moved (aFromScroll is false) or scrolled (aFromScroll is true).
+ */
+ void SynthesizeMouseMove(bool aFromScroll);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEvent(nsIFrame* aFrame, WidgetGUIEvent* aEvent,
+ bool aDontRetargetEvents, nsEventStatus* aEventStatus);
+ bool ShouldIgnoreInvalidation();
+ /**
+ * Notify that we called Paint with PaintFlags::PaintComposite.
+ * Fires on the presshell for the painted widget.
+ * This is issued at a time when it's safe to modify widget geometry.
+ */
+ MOZ_CAN_RUN_SCRIPT void DidPaintWindow();
+
+ bool IsVisible() const;
+ bool IsUnderHiddenEmbedderElement() const {
+ return mUnderHiddenEmbedderElement;
+ }
+ void SetIsUnderHiddenEmbedderElement(bool aUnderHiddenEmbedderElement) {
+ mUnderHiddenEmbedderElement = aUnderHiddenEmbedderElement;
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void DispatchSynthMouseMove(WidgetGUIEvent* aEvent);
+
+ /* Temporarily ignore the Displayport for better paint performance. We
+ * trigger a repaint once suppression is disabled. Without that
+ * the displayport may get left at the suppressed size for an extended
+ * period of time and result in unnecessary checkerboarding (see bug
+ * 1255054). */
+ void SuppressDisplayport(bool aEnabled);
+
+ /* Whether or not displayport suppression should be turned on. Note that
+ * this only affects the return value of |IsDisplayportSuppressed()|, and
+ * doesn't change the value of the internal counter.
+ */
+ void RespectDisplayportSuppression(bool aEnabled);
+
+ /* Whether or not the displayport is currently suppressed. */
+ bool IsDisplayportSuppressed();
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aWindowSizes) const;
+
+ /**
+ * Methods that retrieve the cached font inflation preferences.
+ */
+ uint32_t FontSizeInflationEmPerLine() const {
+ return mFontSizeInflationEmPerLine;
+ }
+
+ uint32_t FontSizeInflationMinTwips() const {
+ return mFontSizeInflationMinTwips;
+ }
+
+ uint32_t FontSizeInflationLineThreshold() const {
+ return mFontSizeInflationLineThreshold;
+ }
+
+ bool FontSizeInflationForceEnabled() const {
+ return mFontSizeInflationForceEnabled;
+ }
+
+ bool FontSizeInflationDisabledInMasterProcess() const {
+ return mFontSizeInflationDisabledInMasterProcess;
+ }
+
+ bool FontSizeInflationEnabled() const { return mFontSizeInflationEnabled; }
+
+ /**
+ * Recomputes whether font-size inflation is enabled.
+ */
+ void RecomputeFontSizeInflationEnabled();
+
+ /**
+ * Return true if the most recent interruptible reflow was interrupted.
+ */
+ bool IsReflowInterrupted() const { return mWasLastReflowInterrupted; }
+
+ /**
+ * Return true if the the interruptible reflows have to be suppressed.
+ * This may happen only if if the most recent reflow was interrupted.
+ */
+ bool SuppressInterruptibleReflows() const {
+ return mWasLastReflowInterrupted;
+ }
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Approximate frame visibility tracking public API.
+ //////////////////////////////////////////////////////////////////////////////
+
+ /**
+ * Schedule an update of the list of approximately visible frames "soon".
+ * This lets the refresh driver know that we want a visibility update in the
+ * near future. The refresh driver applies its own heuristics and throttling
+ * to decide when to actually perform the visibility update.
+ */
+ void ScheduleApproximateFrameVisibilityUpdateSoon();
+
+ /**
+ * Schedule an update of the list of approximately visible frames "now". The
+ * update runs asynchronously, but it will be posted to the event loop
+ * immediately. Prefer the "soon" variation of this method when possible, as
+ * this variation ignores the refresh driver's heuristics.
+ */
+ void ScheduleApproximateFrameVisibilityUpdateNow();
+
+ /**
+ * Clears the current list of approximately visible frames on this pres shell
+ * and replaces it with frames that are in the display list @aList.
+ */
+ void RebuildApproximateFrameVisibilityDisplayList(const nsDisplayList& aList);
+ void RebuildApproximateFrameVisibility(nsRect* aRect = nullptr,
+ bool aRemoveOnly = false);
+
+ /**
+ * Ensures @aFrame is in the list of approximately visible frames.
+ */
+ void EnsureFrameInApproximatelyVisibleList(nsIFrame* aFrame);
+
+ /// Removes @aFrame from the list of approximately visible frames if present.
+ void RemoveFrameFromApproximatelyVisibleList(nsIFrame* aFrame);
+
+ /// Whether we should assume all frames are visible.
+ bool AssumeAllFramesVisible();
+
+ /**
+ * Returns whether the document's style set's rule processor for the
+ * specified level of the cascade is shared by multiple style sets.
+ *
+ * @param aSheetType One of the nsIStyleSheetService.*_SHEET constants.
+ */
+ nsresult HasRuleProcessorUsedByMultipleStyleSets(uint32_t aSheetType,
+ bool* aRetVal);
+
+ /**
+ * Returns whether or not the document has ever handled user input
+ */
+ bool HasHandledUserInput() const { return mHasHandledUserInput; }
+
+ MOZ_CAN_RUN_SCRIPT void FireResizeEvent();
+ MOZ_CAN_RUN_SCRIPT void FireResizeEventSync();
+
+ void NativeAnonymousContentRemoved(nsIContent* aAnonContent);
+
+ /**
+ * See HTMLDocument.setKeyPressEventModel() in HTMLDocument.webidl for the
+ * detail.
+ */
+ void SetKeyPressEventModel(uint16_t aKeyPressEventModel) {
+ mForceUseLegacyKeyCodeAndCharCodeValues |=
+ aKeyPressEventModel ==
+ dom::Document_Binding::KEYPRESS_EVENT_MODEL_SPLIT;
+ }
+
+ bool AddRefreshObserver(nsARefreshObserver* aObserver, FlushType aFlushType,
+ const char* aObserverDescription);
+ bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType);
+
+ bool AddPostRefreshObserver(nsAPostRefreshObserver*);
+ bool AddPostRefreshObserver(mozilla::ManagedPostRefreshObserver*) = delete;
+ bool RemovePostRefreshObserver(nsAPostRefreshObserver*);
+ bool RemovePostRefreshObserver(mozilla::ManagedPostRefreshObserver*) = delete;
+
+ // Represents an update to the visual scroll offset that will be sent to APZ.
+ // The update type is used to determine priority compared to other scroll
+ // updates.
+ struct VisualScrollUpdate {
+ nsPoint mVisualScrollOffset;
+ FrameMetrics::ScrollOffsetUpdateType mUpdateType;
+ bool mAcknowledged = false;
+ };
+
+ // Ask APZ in the next transaction to scroll to the given visual viewport
+ // offset (relative to the document).
+ // This is intended to be used when desired in cases where the browser
+ // internally triggers scrolling; scrolling triggered explicitly by web
+ // content (such as via window.scrollTo() should scroll the layout viewport
+ // only).
+ // If scrolling "far away", i.e. not just within the existing layout
+ // viewport, it's recommended to use both nsIScrollableFrame.ScrollTo*()
+ // (via window.scrollTo if calling from JS) *and* this function; otherwise,
+ // temporary checkerboarding may result. If doing this:
+ // * Be sure to call ScrollTo*() first, as a subsequent layout scroll
+ // in the same transaction will cancel the pending visual scroll.
+ // * Keep in mind that ScrollTo*() can tear down the pres shell and
+ // frame tree. Depending on how the pres shell is obtained for the
+ // subsequent ScrollToVisual() call, AutoWeakFrame or similar may
+ // need to be used.
+ // Please request APZ review if adding a new call site.
+ void ScrollToVisual(const nsPoint& aVisualViewportOffset,
+ FrameMetrics::ScrollOffsetUpdateType aUpdateType,
+ ScrollMode aMode);
+ void AcknowledgePendingVisualScrollUpdate();
+ void ClearPendingVisualScrollUpdate();
+ const Maybe<VisualScrollUpdate>& GetPendingVisualScrollUpdate() const {
+ return mPendingVisualScrollUpdate;
+ }
+
+ nsPoint GetLayoutViewportOffset() const;
+ nsSize GetLayoutViewportSize() const;
+
+ /**
+ * Documents belonging to an invisible DocShell must not be painted ever.
+ */
+ bool IsNeverPainting() { return mIsNeverPainting; }
+
+ void SetNeverPainting(bool aNeverPainting) {
+ mIsNeverPainting = aNeverPainting;
+ }
+
+ /**
+ * True if a reflow event has been scheduled, or is going to be scheduled
+ * to run in the future.
+ */
+ bool HasPendingReflow() const {
+ return mObservingLayoutFlushes || mReflowContinueTimer;
+ }
+
+ void SyncWindowProperties(bool aSync);
+ struct WindowSizeConstraints {
+ nsSize mMinSize;
+ nsSize mMaxSize;
+ };
+ WindowSizeConstraints GetWindowSizeConstraints();
+
+ Document* GetPrimaryContentDocument();
+
+ struct MOZ_RAII AutoAssertNoFlush {
+ explicit AutoAssertNoFlush(PresShell& aPresShell)
+ : mPresShell(aPresShell), mOldForbidden(mPresShell.mForbiddenToFlush) {
+ mPresShell.mForbiddenToFlush = true;
+ }
+
+ ~AutoAssertNoFlush() { mPresShell.mForbiddenToFlush = mOldForbidden; }
+
+ PresShell& mPresShell;
+ const bool mOldForbidden;
+ };
+
+ NS_IMETHOD GetSelectionFromScript(RawSelectionType aRawSelectionType,
+ dom::Selection** aSelection) override;
+ dom::Selection* GetSelection(RawSelectionType aRawSelectionType) override;
+
+ NS_IMETHOD SetDisplaySelection(int16_t aToggle) override;
+ NS_IMETHOD GetDisplaySelection(int16_t* aToggle) override;
+ NS_IMETHOD ScrollSelectionIntoView(RawSelectionType aRawSelectionType,
+ SelectionRegion aRegion,
+ int16_t aFlags) override;
+ NS_IMETHOD RepaintSelection(RawSelectionType aRawSelectionType) override;
+ void SelectionWillTakeFocus() override;
+ void SelectionWillLoseFocus() override;
+
+ // Implements the "focus fix-up rule". Returns true if the focus moved (in
+ // which case we might need to update layout again).
+ // See https://github.com/whatwg/html/issues/8225
+ MOZ_CAN_RUN_SCRIPT bool FixUpFocus();
+
+ /**
+ * Set a "resolution" for the document, which if not 1.0 will
+ * allocate more or fewer pixels for rescalable content by a factor
+ * of |resolution| in both dimensions. Return NS_OK iff the
+ * resolution bounds are sane, and the resolution of this was
+ * actually updated.
+ *
+ * Also increase the scale of the content by the same amount
+ * (that's the "AndScaleTo" part).
+ *
+ * The resolution defaults to 1.0.
+ *
+ * |aOrigin| specifies who originated the resolution change. For changes
+ * sent by APZ, pass ResolutionChangeOrigin::Apz. For changes sent by
+ * the main thread, pass ResolutionChangeOrigin::MainThreadAdjustment (similar
+ * to the |aOrigin| parameter of nsIScrollableFrame::ScrollToCSSPixels()).
+ */
+ nsresult SetResolutionAndScaleTo(float aResolution,
+ ResolutionChangeOrigin aOrigin);
+
+ ResolutionChangeOrigin GetLastResolutionChangeOrigin() {
+ return mLastResolutionChangeOrigin;
+ }
+
+ // Widget notificiations
+ void WindowSizeMoveDone();
+
+ void BackingScaleFactorChanged() { mPresContext->UIResolutionChangedSync(); }
+
+ /**
+ * Does any painting work required to update retained paint state, and pushes
+ * it the compositor (if any). Requests a composite, either by scheduling a
+ * remote composite, or invalidating the widget so that we get a call to
+ * SyncPaintFallback from the widget paint event.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void PaintAndRequestComposite(nsView* aView, PaintFlags aFlags);
+
+ /**
+ * Does an immediate paint+composite using the FallbackRenderer (which must
+ * be the current WindowRenderer for the root frame's widget).
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SyncPaintFallback(nsView* aView);
+
+ /**
+ * Notify that we're going to call Paint with PaintFlags::PaintLayers
+ * on the pres shell for a widget (which might not be this one, since
+ * WillPaint is called on all presshells in the same toplevel window as the
+ * painted widget). This is issued at a time when it's safe to modify
+ * widget geometry.
+ */
+ MOZ_CAN_RUN_SCRIPT void WillPaint();
+
+ /**
+ * Ensures that the refresh driver is running, and schedules a view
+ * manager flush on the next tick.
+ */
+ void ScheduleViewManagerFlush();
+
+ // caret handling
+ NS_IMETHOD SetCaretEnabled(bool aInEnable) override;
+ NS_IMETHOD SetCaretReadOnly(bool aReadOnly) override;
+ NS_IMETHOD GetCaretEnabled(bool* aOutEnabled) override;
+ NS_IMETHOD SetCaretVisibilityDuringSelection(bool aVisibility) override;
+ NS_IMETHOD GetCaretVisible(bool* _retval) override;
+
+ /**
+ * Should the images have borders etc. Actual visual effects are determined
+ * by the frames. Visual effects may not effect layout, only display.
+ * Takes effect on next repaint, does not force a repaint itself.
+ *
+ * @param aFlags may be multiple of nsISelectionDisplay::DISPLAY_*.
+ */
+ NS_IMETHOD SetSelectionFlags(int16_t aFlags) override;
+ NS_IMETHOD GetSelectionFlags(int16_t* aFlags) override;
+
+ /**
+ * Gets the current state of non text selection effects
+ * @return current state of non text selection,
+ * as set by SetDisplayNonTextSelection
+ */
+ int16_t GetSelectionFlags() const { return mSelectionFlags; }
+
+ // nsISelectionController
+
+ NS_IMETHOD PhysicalMove(int16_t aDirection, int16_t aAmount,
+ bool aExtend) override;
+ NS_IMETHOD CharacterMove(bool aForward, bool aExtend) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD WordMove(bool aForward,
+ bool aExtend) override;
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD LineMove(bool aForward,
+ bool aExtend) override;
+ NS_IMETHOD IntraLineMove(bool aForward, bool aExtend) override;
+ MOZ_CAN_RUN_SCRIPT
+ NS_IMETHOD PageMove(bool aForward, bool aExtend) override;
+ NS_IMETHOD ScrollPage(bool aForward) override;
+ NS_IMETHOD ScrollLine(bool aForward) override;
+ NS_IMETHOD ScrollCharacter(bool aRight) override;
+ NS_IMETHOD CompleteScroll(bool aForward) override;
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD CompleteMove(bool aForward,
+ bool aExtend) override;
+
+ // Notifies that the state of the document has changed.
+ void DocumentStatesChanged(dom::DocumentState);
+
+ // nsIDocumentObserver
+ NS_DECL_NSIDOCUMENTOBSERVER_BEGINLOAD
+ NS_DECL_NSIDOCUMENTOBSERVER_ENDLOAD
+ NS_DECL_NSIDOCUMENTOBSERVER_CONTENTSTATECHANGED
+
+ // nsIMutationObserver
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTEWILLCHANGE
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+
+ NS_DECL_NSIOBSERVER
+
+ // Inline methods defined in PresShellInlines.h
+ inline void EnsureStyleFlush();
+ inline void SetNeedStyleFlush();
+ inline void SetNeedLayoutFlush();
+ inline void SetNeedThrottledAnimationFlush();
+ inline ServoStyleSet* StyleSet() const;
+
+ /**
+ * Whether we might need a flush for the given flush type. If this
+ * function returns false, we definitely don't need to flush.
+ *
+ * @param aFlushType The flush type to check. This must be
+ * >= FlushType::Style. This also returns true if a throttled
+ * animation flush is required.
+ */
+ bool NeedFlush(FlushType aType) const {
+ // We check mInFlush to handle re-entrant calls to FlushPendingNotifications
+ // by reporting that we always need a flush in that case. Otherwise,
+ // we could end up missing needed flushes, since we clear the mNeedXXXFlush
+ // flags at the top of FlushPendingNotifications.
+ MOZ_ASSERT(aType >= FlushType::Style);
+ return mNeedStyleFlush ||
+ (mNeedLayoutFlush && aType >= FlushType::InterruptibleLayout) ||
+ aType >= FlushType::Display || mNeedThrottledAnimationFlush ||
+ mInFlush;
+ }
+
+ /**
+ * Returns true if we might need to flush layout, even if we haven't scheduled
+ * one yet (as opposed to HasPendingReflow, which returns true if a flush is
+ * scheduled or will soon be scheduled).
+ */
+ bool NeedLayoutFlush() const { return mNeedLayoutFlush; }
+
+ bool NeedStyleFlush() const { return mNeedStyleFlush; }
+
+ /**
+ * Flush pending notifications of the type specified. This method
+ * will not affect the content model; it'll just affect style and
+ * frames. Callers that actually want up-to-date presentation (other
+ * than the document itself) should probably be calling
+ * Document::FlushPendingNotifications.
+ *
+ * This method can execute script, which can destroy this presshell object
+ * unless someone is holding a reference to it on the stack. The presshell
+ * itself will ensure it lives up until the method returns, but callers who
+ * plan to use the presshell after this call should hold a strong ref
+ * themselves!
+ *
+ * @param aType the type of notifications to flush
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void FlushPendingNotifications(FlushType aType) {
+ if (!NeedFlush(aType)) {
+ return;
+ }
+
+ DoFlushPendingNotifications(aType);
+ }
+
+ MOZ_CAN_RUN_SCRIPT
+ void FlushPendingNotifications(ChangesToFlush aType) {
+ if (!NeedFlush(aType.mFlushType)) {
+ return;
+ }
+
+ DoFlushPendingNotifications(aType);
+ }
+
+ /**
+ * Tell the pres shell that a frame needs to be marked dirty and needs
+ * Reflow. It's OK if this is an ancestor of the frame needing reflow as
+ * long as the ancestor chain between them doesn't cross a reflow root.
+ *
+ * The bit to add should be NS_FRAME_IS_DIRTY, NS_FRAME_HAS_DIRTY_CHILDREN
+ * or nsFrameState(0); passing 0 means that dirty bits won't be set on the
+ * frame or its ancestors/descendants, but that intrinsic widths will still
+ * be marked dirty. Passing aIntrinsicDirty = eResize and aBitToAdd = 0
+ * would result in no work being done, so don't do that.
+ */
+ void FrameNeedsReflow(
+ nsIFrame* aFrame, IntrinsicDirty aIntrinsicDirty, nsFrameState aBitToAdd,
+ ReflowRootHandling aRootHandling = ReflowRootHandling::InferFromBitToAdd);
+
+ /**
+ * Calls FrameNeedsReflow on all fixed position children of the root frame.
+ */
+ void MarkFixedFramesForReflow(IntrinsicDirty aIntrinsicDirty);
+
+ void MaybeReflowForInflationScreenSizeChange();
+
+ // This function handles all the work after VisualViewportSize is set
+ // or reset.
+ void CompleteChangeToVisualViewportSize();
+
+ /**
+ * The return value indicates whether the offset actually changed.
+ */
+ bool SetVisualViewportOffset(const nsPoint& aScrollOffset,
+ const nsPoint& aPrevLayoutScrollPos);
+
+ void ResetVisualViewportOffset();
+ nsPoint GetVisualViewportOffset() const {
+ if (mVisualViewportOffset.isSome()) {
+ return *mVisualViewportOffset;
+ }
+ return GetLayoutViewportOffset();
+ }
+ bool IsVisualViewportOffsetSet() const {
+ return mVisualViewportOffset.isSome();
+ }
+
+ void SetVisualViewportSize(nscoord aWidth, nscoord aHeight);
+ void ResetVisualViewportSize();
+ bool IsVisualViewportSizeSet() { return mVisualViewportSizeSet; }
+ nsSize GetVisualViewportSize() {
+ NS_ASSERTION(mVisualViewportSizeSet,
+ "asking for visual viewport size when its not set?");
+ return mVisualViewportSize;
+ }
+
+ nsPoint GetVisualViewportOffsetRelativeToLayoutViewport() const;
+
+ // Returns state of the dynamic toolbar.
+ DynamicToolbarState GetDynamicToolbarState() const {
+ if (!mPresContext) {
+ return DynamicToolbarState::None;
+ }
+
+ return mPresContext->GetDynamicToolbarState();
+ }
+ // Returns the visual viewport size during the dynamic toolbar is being
+ // shown/hidden.
+ nsSize GetVisualViewportSizeUpdatedByDynamicToolbar() const;
+
+ /* Enable/disable author style level. Disabling author style disables the
+ * entire author level of the cascade, including the HTML preshint level.
+ */
+ // XXX these could easily be inlined, but there is a circular #include
+ // problem with nsStyleSet.
+ void SetAuthorStyleDisabled(bool aDisabled);
+ bool GetAuthorStyleDisabled() const;
+
+ // aSheetType is one of the nsIStyleSheetService *_SHEET constants.
+ void NotifyStyleSheetServiceSheetAdded(StyleSheet* aSheet,
+ uint32_t aSheetType);
+ void NotifyStyleSheetServiceSheetRemoved(StyleSheet* aSheet,
+ uint32_t aSheetType);
+
+ // DoReflow returns whether the reflow finished without interruption
+ // If aFrame is not the root frame, the caller must pass a non-null
+ // aOverflowTracker.
+ bool DoReflow(nsIFrame* aFrame, bool aInterruptible,
+ OverflowChangedTracker* aOverflowTracker);
+
+ /**
+ * Add a solid color item to the bottom of aList with frame aFrame and bounds
+ * aBounds. aBackstopColor is composed behind the background color of the
+ * canvas, and it is transparent by default.
+ *
+ * We attempt to make the background color part of the scrolled canvas (to
+ * reduce transparent layers), and if async scrolling is enabled (and the
+ * background is opaque) then we add a second, unscrolled item to handle the
+ * checkerboarding case.
+ */
+ void AddCanvasBackgroundColorItem(
+ nsDisplayListBuilder* aBuilder, nsDisplayList* aList, nsIFrame* aFrame,
+ const nsRect& aBounds, nscolor aBackstopColor = NS_RGBA(0, 0, 0, 0));
+
+ size_t SizeOfTextRuns(MallocSizeOf aMallocSizeOf) const;
+
+ static PresShell* GetShellForEventTarget(nsIFrame* aFrame,
+ nsIContent* aContent);
+ static PresShell* GetShellForTouchEvent(WidgetGUIEvent* aEvent);
+
+ /**
+ * Informs the pres shell that the document is now at the anchor with
+ * the given name. If |aScroll| is true, scrolls the view of the
+ * document so that the anchor with the specified name is displayed at
+ * the top of the window. If |aAnchorName| is empty, then this informs
+ * the pres shell that there is no current target, and |aScroll| must
+ * be false. If |aAdditionalScrollFlags| is ScrollFlags::ScrollSmoothAuto
+ * and |aScroll| is true, the scrolling may be performed with an animation.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult GoToAnchor(const nsAString& aAnchorName, bool aScroll,
+ ScrollFlags aAdditionalScrollFlags = ScrollFlags::None);
+
+ /**
+ * Tells the presshell to scroll again to the last anchor scrolled to by
+ * GoToAnchor, if any. This scroll only happens if the scroll
+ * position has not changed since the last GoToAnchor (modulo scroll anchoring
+ * adjustments). This is called by nsDocumentViewer::LoadComplete. This clears
+ * the last anchor scrolled to by GoToAnchor (we don't want to keep it alive
+ * if it's removed from the DOM), so don't call this more than once.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult ScrollToAnchor();
+
+ /**
+ * When scroll anchoring adjusts positions in the root frame during page load,
+ * it may move our scroll position in the root frame.
+ *
+ * While that's generally desirable, when scrolling to an anchor via an id-ref
+ * we have a more direct target. If the id-ref points to something that cannot
+ * be selected as a scroll anchor container (like an image or an inline), we
+ * may select a node following it as a scroll anchor, and if then stuff is
+ * inserted on top, we may end up moving the id-ref element offscreen to the
+ * top inadvertently.
+ *
+ * On page load, the document viewer will call ScrollToAnchor(), and will only
+ * scroll to the anchor again if the scroll position is not changed. We don't
+ * want scroll anchoring adjustments to prevent this, so account for them.
+ */
+ void RootScrollFrameAdjusted(nscoord aYAdjustment) {
+ if (mLastAnchorScrolledTo) {
+ mLastAnchorScrollPositionY += aYAdjustment;
+ }
+ }
+
+ /**
+ * Scrolls the view of the document so that the primary frame of the content
+ * is displayed in the window. Layout is flushed before scrolling.
+ *
+ * @param aContent The content object of which primary frame should be
+ * scrolled into view.
+ * @param aVertical How to align the frame vertically and when to do so.
+ * This is a ScrollAxis of Where and When.
+ * @param aHorizontal How to align the frame horizontally and when to do so.
+ * This is a ScrollAxis of Where and When.
+ * @param aScrollFlags If ScrollFlags::ScrollFirstAncestorOnly is set,
+ * only the nearest scrollable ancestor is scrolled,
+ * otherwise all scrollable ancestors may be scrolled
+ * if necessary. If ScrollFlags::ScrollOverflowHidden
+ * is set then we may scroll in a direction even if
+ * overflow:hidden is specified in that direction;
+ * otherwise we will not scroll in that direction when
+ * overflow:hidden is set for that direction. If
+ * ScrollFlags::ScrollNoParentFrames is set then we
+ * only scroll nodes in this document, not in any
+ * parent documents which contain this document in a
+ * iframe or the like. If ScrollFlags::ScrollSmooth
+ * is set and CSSOM-VIEW scroll-behavior is enabled,
+ * we will scroll smoothly using
+ * nsIScrollableFrame::ScrollMode::SMOOTH_MSD;
+ * otherwise, nsIScrollableFrame::ScrollMode::INSTANT
+ * will be used. If ScrollFlags::ScrollSmoothAuto is
+ * set, the CSSOM-View scroll-behavior attribute is
+ * set to 'smooth' on the scroll frame, and CSSOM-VIEW
+ * scroll-behavior is enabled, we will scroll smoothly
+ * using nsIScrollableFrame::ScrollMode::SMOOTH_MSD;
+ * otherwise, nsIScrollableFrame::ScrollMode::INSTANT
+ * will be used.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult ScrollContentIntoView(nsIContent* aContent, ScrollAxis aVertical,
+ ScrollAxis aHorizontal,
+ ScrollFlags aScrollFlags);
+
+ /**
+ * When capturing content is set, it traps all mouse events and retargets
+ * them at this content node. If capturing is not allowed
+ * (gCaptureInfo.mAllowed is false), then capturing is not set. However, if
+ * the CaptureFlags::IgnoreAllowedState is set, the allowed state is ignored
+ * and capturing is set regardless. To disable capture, pass null for the
+ * value of aContent.
+ *
+ * If CaptureFlags::RetargetedToElement is set, all mouse events are
+ * targeted at aContent only. Otherwise, mouse events are targeted at
+ * aContent or its descendants. That is, descendants of aContent receive
+ * mouse events as they normally would, but mouse events outside of aContent
+ * are retargeted to aContent.
+ *
+ * If CaptureFlags::PreventDragStart is set then drags are prevented from
+ * starting while this capture is active.
+ *
+ * If CaptureFlags::PointerLock is set, similar to
+ * CaptureFlags::RetargetToElement, then events are targeted at aContent,
+ * but capturing is held more strongly (i.e., calls to SetCapturingContent()
+ * won't unlock unless CaptureFlags::PointerLock is set again).
+ */
+ static void SetCapturingContent(nsIContent* aContent, CaptureFlags aFlags,
+ WidgetEvent* aEvent = nullptr);
+
+ /**
+ * Alias for SetCapturingContent(nullptr, CaptureFlags::None) for making
+ * callers what they do clearer.
+ */
+ static void ReleaseCapturingContent() {
+ PresShell::SetCapturingContent(nullptr, CaptureFlags::None);
+ }
+
+ static void ReleaseCapturingRemoteTarget(dom::BrowserParent* aBrowserParent) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ if (sCapturingContentInfo.mRemoteTarget == aBrowserParent) {
+ sCapturingContentInfo.mRemoteTarget = nullptr;
+ }
+ }
+
+ // Called at the end of nsLayoutUtils::PaintFrame() if we were painting to
+ // the widget.
+ // This is used to clear any pending visual scroll updates that have been
+ // acknowledged, to make sure they don't stick around for the next paint.
+ void EndPaint();
+
+ /**
+ * Tell the presshell that the given frame's reflow was interrupted. This
+ * will mark as having dirty children a path from the given frame (inclusive)
+ * to the nearest ancestor with a dirty subtree, or to the reflow root
+ * currently being reflowed if no such ancestor exists (inclusive). This is
+ * to be done immediately after reflow of the current reflow root completes.
+ * This method must only be called during reflow, and the frame it's being
+ * called on must be in the process of being reflowed when it's called. This
+ * method doesn't mark any intrinsic widths dirty and doesn't add any bits
+ * other than NS_FRAME_HAS_DIRTY_CHILDREN.
+ */
+ void FrameNeedsToContinueReflow(nsIFrame* aFrame);
+
+ /**
+ * Notification sent by a frame informing the pres shell that it is about to
+ * be destroyed.
+ * This allows any outstanding references to the frame to be cleaned up
+ */
+ void NotifyDestroyingFrame(nsIFrame* aFrame);
+
+ bool GetZoomableByAPZ() const;
+
+ bool ReflowForHiddenContentIfNeeded();
+ void UpdateHiddenContentInForcedLayout(nsIFrame*);
+ /**
+ * If this frame has content hidden via `content-visibilty` that has a pending
+ * reflow, force the content to reflow immediately.
+ */
+ void EnsureReflowIfFrameHasHiddenContent(nsIFrame*);
+
+ /**
+ * Whether or not this presshell is is forcing a reflow of hidden content in
+ * this frame via EnsureReflowIfFrameHasHiddenContent().
+ */
+ bool IsForcingLayoutForHiddenContent(const nsIFrame*) const;
+
+ void RegisterContentVisibilityAutoFrame(nsIFrame* aFrame) {
+ mContentVisibilityAutoFrames.Insert(aFrame);
+ }
+ void UnregisterContentVisibilityAutoFrame(nsIFrame* aFrame) {
+ mContentVisibilityAutoFrames.Remove(aFrame);
+ }
+ bool HasContentVisibilityAutoFrames() const {
+ return !mContentVisibilityAutoFrames.IsEmpty();
+ }
+
+ void UpdateRelevancyOfContentVisibilityAutoFrames();
+ void ScheduleContentRelevancyUpdate(ContentRelevancyReason aReason);
+ void UpdateContentRelevancyImmediately(ContentRelevancyReason aReason);
+
+ // Determination of proximity to the viewport.
+ // Refer to "update the rendering: step 14", see
+ // https://html.spec.whatwg.org/#update-the-rendering
+ struct ProximityToViewportResult {
+ bool mHadInitialDetermination = false;
+ bool mAnyScrollIntoViewFlag = false;
+ };
+ ProximityToViewportResult DetermineProximityToViewport();
+
+ void ClearTemporarilyVisibleForScrolledIntoViewDescendantFlags() const;
+
+ private:
+ ~PresShell();
+
+ void SetIsActive(bool aIsActive);
+ bool ComputeActiveness() const;
+
+ MOZ_CAN_RUN_SCRIPT
+ void PaintInternal(nsView* aViewToPaint, PaintInternalFlags aFlags);
+
+ /**
+ * Refresh observer management.
+ */
+ void DoObserveStyleFlushes();
+ void DoObserveLayoutFlushes();
+
+ /**
+ * Does the actual work of figuring out the current state of font size
+ * inflation.
+ */
+ bool DetermineFontSizeInflationState();
+
+ void RecordAlloc(void* aPtr) {
+#ifdef DEBUG
+ if (!mAllocatedPointers) {
+ return; // Hash set was presumably freed to avert OOM.
+ }
+ MOZ_ASSERT(!mAllocatedPointers->Contains(aPtr));
+ if (!mAllocatedPointers->Insert(aPtr, fallible)) {
+ // Yikes! We're nearly out of memory, and this insertion would've pushed
+ // us over the ledge. At this point, we discard & stop using this set,
+ // since we don't have enough memory to keep it accurate from this point
+ // onwards. Hopefully this helps relieve the memory pressure a bit, too.
+ mAllocatedPointers = nullptr;
+ }
+#endif
+ }
+
+ void RecordFree(void* aPtr) {
+#ifdef DEBUG
+ if (!mAllocatedPointers) {
+ return; // Hash set was presumably freed to avert OOM.
+ }
+ MOZ_ASSERT(mAllocatedPointers->Contains(aPtr));
+ mAllocatedPointers->Remove(aPtr);
+#endif
+ }
+
+ void PushCurrentEventInfo(nsIFrame* aFrame, nsIContent* aContent);
+ void PopCurrentEventInfo();
+ nsIContent* GetCurrentEventContent();
+
+ friend class ::nsRefreshDriver;
+ friend class ::nsAutoCauseReflowNotifier;
+
+ void WillCauseReflow();
+ MOZ_CAN_RUN_SCRIPT void DidCauseReflow();
+
+ void CancelPostedReflowCallbacks();
+ void FlushPendingScrollAnchorAdjustments();
+
+ void SetPendingVisualScrollUpdate(
+ const nsPoint& aVisualViewportOffset,
+ FrameMetrics::ScrollOffsetUpdateType aUpdateType);
+
+#ifdef MOZ_REFLOW_PERF
+ UniquePtr<ReflowCountMgr> mReflowCountMgr;
+#endif
+
+ void WillDoReflow();
+
+ // This data is stored as a content property (nsGkAtoms::scrolling) on
+ // mContentToScrollTo when we have a pending ScrollIntoView.
+ struct ScrollIntoViewData {
+ ScrollAxis mContentScrollVAxis;
+ ScrollAxis mContentScrollHAxis;
+ ScrollFlags mContentToScrollToFlags;
+ };
+
+ static LazyLogModule gLog;
+
+ DOMHighResTimeStamp GetPerformanceNowUnclamped();
+
+ // The callback for the mReflowContinueTimer timer.
+ static void sReflowContinueCallback(nsITimer* aTimer, void* aPresShell);
+ bool ScheduleReflowOffTimer();
+ // MaybeScheduleReflow checks if posting a reflow is needed, then checks if
+ // the last reflow was interrupted. In the interrupted case ScheduleReflow is
+ // called off a timer, otherwise it is called directly.
+ void MaybeScheduleReflow();
+ // Actually schedules a reflow. This should only be called by
+ // MaybeScheduleReflow and the reflow timer ScheduleReflowOffTimer
+ // sets up.
+ void ScheduleReflow();
+
+ friend class ::AutoPointerEventTargetUpdater;
+
+ // ProcessReflowCommands returns whether we processed all our dirty roots
+ // without interruptions.
+ MOZ_CAN_RUN_SCRIPT bool ProcessReflowCommands(bool aInterruptible);
+
+ /**
+ * Callback handler for whether reflow happened.
+ *
+ * @param aInterruptible Whether or not reflow interruption is allowed.
+ */
+ MOZ_CAN_RUN_SCRIPT void DidDoReflow(bool aInterruptible);
+
+ MOZ_CAN_RUN_SCRIPT void HandlePostedReflowCallbacks(bool aInterruptible);
+
+ /**
+ * Helper for ScrollContentIntoView()
+ */
+ MOZ_CAN_RUN_SCRIPT void DoScrollContentIntoView();
+
+ /**
+ * Methods to handle changes to user and UA sheet lists that we get
+ * notified about.
+ */
+ void AddUserSheet(StyleSheet*);
+ void AddAgentSheet(StyleSheet*);
+ void AddAuthorSheet(StyleSheet*);
+
+ /**
+ * Initialize cached font inflation preference values and do an initial
+ * computation to determine if font inflation is enabled.
+ *
+ * @see nsLayoutUtils::sFontSizeInflationEmPerLine
+ * @see nsLayoutUtils::sFontSizeInflationMinTwips
+ * @see nsLayoutUtils::sFontSizeInflationLineThreshold
+ */
+ void SetupFontInflation();
+
+ /**
+ * Implementation methods for FlushPendingNotifications.
+ */
+ MOZ_CAN_RUN_SCRIPT void DoFlushPendingNotifications(FlushType aType);
+ MOZ_CAN_RUN_SCRIPT void DoFlushPendingNotifications(ChangesToFlush aType);
+
+ struct RenderingState {
+ explicit RenderingState(PresShell* aPresShell)
+ : mResolution(aPresShell->mResolution),
+ mRenderingStateFlags(aPresShell->mRenderingStateFlags) {}
+ Maybe<float> mResolution;
+ RenderingStateFlags mRenderingStateFlags;
+ };
+
+ struct AutoSaveRestoreRenderingState {
+ explicit AutoSaveRestoreRenderingState(PresShell* aPresShell)
+ : mPresShell(aPresShell), mOldState(aPresShell) {}
+
+ ~AutoSaveRestoreRenderingState() {
+ mPresShell->mRenderingStateFlags = mOldState.mRenderingStateFlags;
+ mPresShell->mResolution = mOldState.mResolution;
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfResolutionChange(mPresShell,
+ mPresShell->GetResolution());
+ }
+#endif
+ }
+
+ PresShell* mPresShell;
+ RenderingState mOldState;
+ };
+ void SetRenderingState(const RenderingState& aState);
+
+ friend class ::nsPresShellEventCB;
+
+ // methods for painting a range to an offscreen buffer
+
+ // given a display list, clip the items within the list to
+ // the range
+ nsRect ClipListToRange(nsDisplayListBuilder* aBuilder, nsDisplayList* aList,
+ nsRange* aRange);
+
+ // create a RangePaintInfo for the range aRange containing the
+ // display list needed to paint the range to a surface
+ UniquePtr<RangePaintInfo> CreateRangePaintInfo(nsRange* aRange,
+ nsRect& aSurfaceRect,
+ bool aForPrimarySelection);
+
+ /*
+ * Paint the items to a new surface and return it.
+ *
+ * aSelection - selection being painted, if any
+ * aRegion - clip region, if any
+ * aArea - area that the surface occupies, relative to the root frame
+ * aPoint - reference point, typically the mouse position
+ * aScreenRect - [out] set to the area of the screen the painted area should
+ * be displayed at
+ * aFlags - set RenderImageFlags::AutoScale to scale down large images, but
+ * it must not be set if a custom image was specified
+ */
+ already_AddRefed<SourceSurface> PaintRangePaintInfo(
+ const nsTArray<UniquePtr<RangePaintInfo>>& aItems,
+ dom::Selection* aSelection, const Maybe<CSSIntRegion>& aRegion,
+ nsRect aArea, const LayoutDeviceIntPoint aPoint,
+ LayoutDeviceIntRect* aScreenRect, RenderImageFlags aFlags);
+
+ // Hide a view if it is a popup
+ void HideViewIfPopup(nsView* aView);
+
+ // Utility method to restore the root scrollframe state
+ void RestoreRootScrollPosition();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void MaybeReleaseCapturingContent();
+
+ class DelayedEvent {
+ public:
+ virtual ~DelayedEvent() = default;
+ virtual void Dispatch() {}
+ virtual bool IsKeyPressEvent() { return false; }
+ };
+
+ class DelayedInputEvent : public DelayedEvent {
+ public:
+ void Dispatch() override;
+
+ protected:
+ DelayedInputEvent();
+ ~DelayedInputEvent() override;
+
+ WidgetInputEvent* mEvent;
+ };
+
+ class DelayedMouseEvent : public DelayedInputEvent {
+ public:
+ explicit DelayedMouseEvent(WidgetMouseEvent* aEvent);
+ };
+
+ class DelayedKeyEvent : public DelayedInputEvent {
+ public:
+ explicit DelayedKeyEvent(WidgetKeyboardEvent* aEvent);
+ bool IsKeyPressEvent() override;
+ };
+
+ /**
+ * return the nsPoint represents the location of the mouse event relative to
+ * the root document in visual coordinates
+ */
+ nsPoint GetEventLocation(const WidgetMouseEvent& aEvent) const;
+
+ // Check if aEvent is a mouse event and record the mouse location for later
+ // synth mouse moves.
+ void RecordPointerLocation(WidgetGUIEvent* aEvent);
+ inline bool MouseLocationWasSetBySynthesizedMouseEventForTests() const;
+ class nsSynthMouseMoveEvent final : public nsARefreshObserver {
+ public:
+ nsSynthMouseMoveEvent(PresShell* aPresShell, bool aFromScroll)
+ : mPresShell(aPresShell), mFromScroll(aFromScroll) {
+ NS_ASSERTION(mPresShell, "null parameter");
+ }
+
+ private:
+ // Private destructor, to discourage deletion outside of Release():
+ ~nsSynthMouseMoveEvent() { Revoke(); }
+
+ public:
+ NS_INLINE_DECL_REFCOUNTING(nsSynthMouseMoveEvent, override)
+
+ void Revoke();
+
+ MOZ_CAN_RUN_SCRIPT
+ void WillRefresh(TimeStamp aTime) override { Run(); }
+
+ MOZ_CAN_RUN_SCRIPT void Run() {
+ if (mPresShell) {
+ RefPtr<PresShell> shell = mPresShell;
+ shell->ProcessSynthMouseMoveEvent(mFromScroll);
+ }
+ }
+
+ private:
+ PresShell* mPresShell;
+ bool mFromScroll;
+ };
+ MOZ_CAN_RUN_SCRIPT void ProcessSynthMouseMoveEvent(bool aFromScroll);
+
+ void UpdateImageLockingState();
+
+ already_AddRefed<PresShell> GetParentPresShellForEventHandling();
+
+ /**
+ * EventHandler is implementation of PresShell::HandleEvent().
+ */
+ class MOZ_STACK_CLASS EventHandler final {
+ public:
+ EventHandler() = delete;
+ EventHandler(const EventHandler& aOther) = delete;
+ explicit EventHandler(PresShell& aPresShell)
+ : mPresShell(aPresShell), mCurrentEventInfoSetter(nullptr) {}
+ explicit EventHandler(RefPtr<PresShell>&& aPresShell)
+ : mPresShell(std::move(aPresShell)), mCurrentEventInfoSetter(nullptr) {}
+
+ /**
+ * HandleEvent() may dispatch aGUIEvent. This may redirect the event to
+ * another PresShell, or the event may be handled by other classes like
+ * AccessibleCaretEventHub, or discarded. Otherwise, this sets current
+ * event info of mPresShell and calls HandleEventWithCurrentEventInfo()
+ * to dispatch the event into the DOM tree.
+ *
+ * @param aFrameForPresShell The frame for PresShell. If PresShell
+ * has root frame, it should be set.
+ * Otherwise, a frame which contains the
+ * PresShell should be set instead. I.e.,
+ * in the latter case, the frame is in
+ * a parent document.
+ * @param aGUIEvent Event to be handled. Must be a trusted
+ * event.
+ * @param aDontRetargetEvents true if this shouldn't redirect the
+ * event to different PresShell.
+ * false if this can redirect the event to
+ * different PresShell.
+ * @param aEventStatus [in/out] EventStatus of aGUIEvent.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEvent(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent, bool aDontRetargetEvents,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * HandleEventWithTarget() tries to dispatch aEvent on aContent after
+ * setting current event target content to aNewEventContent and current
+ * event frame to aNewEventFrame temporarily. Note that this supports
+ * WidgetEvent, not WidgetGUIEvent. So, you can dispatch a simple event
+ * with this.
+ *
+ * @param aEvent Event to be dispatched. Must be a
+ * trusted event.
+ * @param aNewEventFrame Temporal new event frame.
+ * @param aNewEventContent Temporal new event content.
+ * @param aEventStatus [in/out] EventStuatus of aEvent.
+ * @param aIsHandlingNativeEvent true if aEvent represents a native
+ * event.
+ * @param aTargetContent This is used only when aEvent is a
+ * pointer event. If
+ * PresShell::mPointerEventTarget is
+ * changed during dispatching aEvent,
+ * this is set to the new target.
+ * @param aOverrideClickTarget Override click event target.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventWithTarget(WidgetEvent* aEvent,
+ nsIFrame* aNewEventFrame,
+ nsIContent* aNewEventContent,
+ nsEventStatus* aEventStatus,
+ bool aIsHandlingNativeEvent,
+ nsIContent** aTargetContent,
+ nsIContent* aOverrideClickTarget);
+
+ /**
+ * OnPresShellDestroy() is called when every PresShell instance is being
+ * destroyed.
+ */
+ static inline void OnPresShellDestroy(Document* aDocument);
+
+ private:
+ static bool InZombieDocument(nsIContent* aContent);
+ static nsIFrame* GetNearestFrameContainingPresShell(PresShell* aPresShell);
+ static nsIPrincipal* GetDocumentPrincipalToCompareWithBlacklist(
+ PresShell& aPresShell);
+
+ /**
+ * HandleEventUsingCoordinates() handles aGUIEvent whose
+ * IsUsingCoordinates() returns true with the following helper methods.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details.
+ * @param aGUIEvent The handling event. Make sure that
+ * its IsUsingCoordinates() returns true.
+ * @param aEventStatus The status of aGUIEvent.
+ * @param aDontRetargetEvents true if we've already retarget document.
+ * Otherwise, false.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventUsingCoordinates(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus,
+ bool aDontRetargetEvents);
+
+ /**
+ * EventTargetData struct stores a set of a PresShell (event handler),
+ * a frame (to handle the event) and a content (event target for the frame).
+ */
+ struct MOZ_STACK_CLASS EventTargetData final {
+ EventTargetData() = delete;
+ EventTargetData(const EventTargetData& aOther) = delete;
+ explicit EventTargetData(nsIFrame* aFrameToHandleEvent) {
+ SetFrameAndComputePresShell(aFrameToHandleEvent);
+ }
+
+ void SetFrameAndComputePresShell(nsIFrame* aFrameToHandleEvent);
+ void SetFrameAndComputePresShellAndContent(nsIFrame* aFrameToHandleEvent,
+ WidgetGUIEvent* aGUIEvent);
+ void SetContentForEventFromFrame(WidgetGUIEvent* aGUIEvent);
+
+ nsPresContext* GetPresContext() const {
+ return mPresShell ? mPresShell->GetPresContext() : nullptr;
+ };
+ EventStateManager* GetEventStateManager() const {
+ nsPresContext* presContext = GetPresContext();
+ return presContext ? presContext->EventStateManager() : nullptr;
+ }
+ Document* GetDocument() const {
+ return mPresShell ? mPresShell->GetDocument() : nullptr;
+ }
+
+ /**
+ * Return content of the frame if and only if a frame is set.
+ * I.e., this may return non-element node even when GetContent() returns
+ * an element node.
+ */
+ nsIContent* GetFrameContent() const;
+
+ nsIFrame* GetFrame() const { return mFrame; }
+ nsIContent* GetContent() const { return mContent; }
+
+ /**
+ * Set the event target content and the topmost frame at the event point.
+ * This checks whether the relation is correct if aContent is not nullptr.
+ * If you set aGUIEvent, the check is done with strict way, but otherwise,
+ * it checks whether aContent is a proper inclusive ancestor of
+ * mFrame->GetContent() or not.
+ */
+ void SetFrameAndContent(nsIFrame* aFrame, nsIContent* aContent = nullptr,
+ const WidgetGUIEvent* aGUIEvent = nullptr) {
+ mFrame = aFrame;
+ mContent = aContent ? aContent : GetFrameContent();
+ AssertIfEventTargetContentAndFrameContentMismatch(aGUIEvent);
+ }
+
+ /**
+ * Set the event target content and clear the frame.
+ */
+ void SetContent(nsIContent* aContent) {
+ mContent = aContent;
+ if (mFrame && GetFrameContent() != aContent) {
+ mFrame = nullptr;
+ }
+ }
+
+ /**
+ * MaybeRetargetToActiveDocument() tries retarget aGUIEvent into
+ * active document if there is. Note that this does not support to
+ * retarget mContent. Make sure it is nullptr before calling this.
+ *
+ * @param aGUIEvent The handling event.
+ * @return true if retargetted.
+ */
+ bool MaybeRetargetToActiveDocument(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * ComputeElementFromFrame() computes mContent for aGUIEvent. If
+ * mContent is set by this method, mContent is always nullptr or an
+ * Element.
+ *
+ * @param aGUIEvent The handling event.
+ * @return true if caller can keep handling the event.
+ * Otherwise, false.
+ * Note that even if this returns true, mContent
+ * may be nullptr.
+ */
+ bool ComputeElementFromFrame(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * UpdateTouchEventTarget() updates mFrame, mPresShell and mContent if
+ * aGUIEvent is a touch event and there is new proper target.
+ *
+ * @param aGUIEvent The handled event. If it's not a touch event,
+ * this method does nothing.
+ */
+ void UpdateTouchEventTarget(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * UpdateWheelEventTarget() updates mFrame, mPresShell, and mContent if
+ * aGUIEvent is a wheel event and aGUIEvent should be grouped with prior
+ * wheel events.
+ *
+ * @param aGUIEvent The handled event. If it's not a wheel event,
+ * this method does nothing.
+ */
+ void UpdateWheelEventTarget(WidgetGUIEvent* aGUIEvent);
+
+ private:
+ void AssertIfEventTargetContentAndFrameContentMismatch(
+ const WidgetGUIEvent* aGUIEvent = nullptr) const;
+
+ public:
+ RefPtr<PresShell> mPresShell;
+ nsCOMPtr<nsIContent> mOverrideClickTarget;
+
+ private:
+ nsIFrame* mFrame = nullptr;
+ // mContent is the event target content for mFrame->GetContent().
+ // This may be nullptr even if mFrame is not nullptr.
+ // This may be an ancestor element of mFrame->GetContent() or native
+ // anonymous root content parent.
+ // This may be not an ancestor element of mFrame->GetContent() if
+ // mFrame->GetContentForEvent() returns such element. E.g., clicking in
+ // <area>, mContent is the <area> but mFrame->GetContent() is an <img>.
+ nsCOMPtr<nsIContent> mContent;
+ };
+
+ /**
+ * MaybeFlushPendingNotifications() maybe flush pending notifications if
+ * aGUIEvent should be handled with the latest layout.
+ *
+ * @param aGUIEvent The handling event.
+ * @return true if this actually flushes pending
+ * layout and that has caused changing the
+ * layout.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool MaybeFlushPendingNotifications(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * GetFrameToHandleNonTouchEvent() returns a frame to handle the event.
+ * This may flush pending layout if the target is in child PresShell.
+ *
+ * @param aRootFrameToHandleEvent The root frame to handle the event.
+ * @param aGUIEvent The handling event.
+ * @return The frame which should handle the
+ * event. nullptr if the caller should
+ * stop handling the event.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsIFrame* GetFrameToHandleNonTouchEvent(nsIFrame* aRootFrameToHandleEvent,
+ WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * ComputeEventTargetFrameAndPresShellAtEventPoint() computes event
+ * target frame at the event point of aGUIEvent and set it to
+ * aEventTargetData.
+ *
+ * @param aRootFrameToHandleEvent The root frame to handle aGUIEvent.
+ * @param aGUIEvent The handling event.
+ * @param aEventTargetData [out] Its frame and PresShell will
+ * be set.
+ * @return true if the caller can handle the
+ * event. Otherwise, false.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool ComputeEventTargetFrameAndPresShellAtEventPoint(
+ nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
+ EventTargetData* aEventTargetData);
+
+ /**
+ * DispatchPrecedingPointerEvent() dispatches preceding pointer event for
+ * aGUIEvent if Pointer Events is enabled.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details.
+ * @param aGUIEvent The handled event.
+ * @param aPointerCapturingContent The content which is capturing pointer
+ * events if there is. Otherwise, nullptr.
+ * @param aDontRetargetEvents Set aDontRetargetEvents of
+ * HandleEvent() which called this method.
+ * @param aEventTargetData [in/out] Event target data of
+ * aGUIEvent. If pointer event listeners
+ * change the DOM tree or reframe the
+ * target, updated by this method.
+ * @param aEventStatus [in/out] The event status of aGUIEvent.
+ * @return true if the caller can handle the
+ * event. Otherwise, false.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool DispatchPrecedingPointerEvent(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsIContent* aPointerCapturingContent,
+ bool aDontRetargetEvents,
+ EventTargetData* aEventTargetData,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * MaybeDiscardEvent() checks whether it's safe to handle aGUIEvent right
+ * now. If it's not safe, this may notify somebody of discarding event if
+ * necessary.
+ *
+ * @param aGUIEvent Handling event.
+ * @return true if it's not safe to handle the event.
+ */
+ bool MaybeDiscardEvent(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * GetCapturingContentFor() returns capturing content for aGUIEvent.
+ * If aGUIEvent is not related to capturing, this returns nullptr.
+ */
+ static nsIContent* GetCapturingContentFor(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * GetRetargetEventDocument() returns a document if aGUIEvent should be
+ * handled in another document.
+ *
+ * @param aGUIEvent Handling event.
+ * @param aRetargetEventDocument Document which should handle aGUIEvent.
+ * @return true if caller can keep handling
+ * aGUIEvent.
+ */
+ bool GetRetargetEventDocument(WidgetGUIEvent* aGUIEvent,
+ Document** aRetargetEventDocument);
+
+ /**
+ * GetFrameForHandlingEventWith() returns a frame which should be used as
+ * aFrameForPresShell of HandleEvent(). See @return for the details.
+ *
+ * @param aGUIEvent Handling event.
+ * @param aRetargetDocument Document which aGUIEvent should be
+ * fired on. Typically, should be result
+ * of GetRetargetEventDocument().
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details.
+ * @return nullptr if caller should stop handling
+ * the event.
+ * aFrameForPresShell if caller should
+ * keep handling the event by itself.
+ * Otherwise, caller should handle it with
+ * another PresShell which is result of
+ * nsIFrame::PresContext()->GetPresShell().
+ */
+ nsIFrame* GetFrameForHandlingEventWith(WidgetGUIEvent* aGUIEvent,
+ Document* aRetargetDocument,
+ nsIFrame* aFrameForPresShell);
+
+ /**
+ * MaybeHandleEventWithAnotherPresShell() may handle aGUIEvent with another
+ * PresShell.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details.
+ * @param aGUIEvent Handling event.
+ * @param aEventStatus [in/out] EventStatus of aGUIEvent.
+ * @param aRv [out] Returns error if this gets an
+ * error handling the event.
+ * @return false if caller needs to keep handling
+ * the event by itself.
+ * true if caller shouldn't keep handling
+ * the event. Note that when no PresShell
+ * can handle the event, this returns true.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool MaybeHandleEventWithAnotherPresShell(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus,
+ nsresult* aRv);
+
+ MOZ_CAN_RUN_SCRIPT
+ nsresult RetargetEventToParent(WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * MaybeHandleEventWithAccessibleCaret() may handle aGUIEvent with
+ * AccessibleCaretEventHub if it's necessary.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See explanation of
+ * HandleEvent() for the details.
+ * @param aGUIEvent Event may be handled by AccessibleCaretEventHub.
+ * @param aEventStatus [in/out] EventStatus of aGUIEvent.
+ * @return true if AccessibleCaretEventHub handled the
+ * event and caller shouldn't keep handling it.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool MaybeHandleEventWithAccessibleCaret(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * MaybeDiscardOrDelayKeyboardEvent() may discared or put aGUIEvent into
+ * the delayed event queue if it's a keyboard event and if we should do so.
+ * If aGUIEvent is not a keyboard event, this does nothing.
+ *
+ * @param aGUIEvent The handling event.
+ * @return true if this method discard the event or
+ * put it into the delayed event queue.
+ */
+ bool MaybeDiscardOrDelayKeyboardEvent(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * MaybeDiscardOrDelayMouseEvent() may discard or put aGUIEvent into the
+ * delayed event queue if it's a mouse event and if we should do so.
+ * If aGUIEvent is not a mouse event, this does nothing.
+ * If there is suppressed event listener like debugger of devtools, this
+ * notifies it of the event after discard or put it into the delayed
+ * event queue.
+ *
+ * @param aFrameToHandleEvent The frame to handle aGUIEvent.
+ * @param aGUIEvent The handling event.
+ * @return true if this method discard the event
+ * or put it into the delayed event queue.
+ */
+ bool MaybeDiscardOrDelayMouseEvent(nsIFrame* aFrameToHandleEvent,
+ WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * MaybeFlushThrottledStyles() tries to flush pending animation. If it's
+ * flushed and then aFrameForPresShell is destroyed, returns new frame
+ * which contains mPresShell.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details. This can be nullptr.
+ * @return Maybe new frame for mPresShell.
+ * If aFrameForPresShell is not nullptr
+ * and hasn't been destroyed, returns
+ * aFrameForPresShell as-is.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsIFrame* MaybeFlushThrottledStyles(nsIFrame* aFrameForPresShell);
+
+ /**
+ * ComputeRootFrameToHandleEvent() returns root frame to handle the event.
+ * For example, if there is a popup, this returns the popup frame.
+ * If there is capturing content and it's in a scrolled frame, returns
+ * the scrolled frame.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for
+ * the details.
+ * @param aGUIEvent The handling event.
+ * @param aCapturingContent Capturing content if there is.
+ * nullptr, otherwise.
+ * @param aIsCapturingContentIgnored [out] true if aCapturingContent
+ * is not nullptr but it should be
+ * ignored to handle the event.
+ * @param aIsCaptureRetargeted [out] true if aCapturingContent
+ * is not nullptr but it's
+ * retargeted.
+ * @return Root frame to handle the event.
+ */
+ nsIFrame* ComputeRootFrameToHandleEvent(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsIContent* aCapturingContent,
+ bool* aIsCapturingContentIgnored,
+ bool* aIsCaptureRetargeted);
+
+ /**
+ * ComputeRootFrameToHandleEventWithPopup() returns popup frame if there
+ * is a popup and we should handle the event in it. Otherwise, returns
+ * aRootFrameToHandleEvent.
+ *
+ * @param aRootFrameToHandleEvent Candidate root frame to handle
+ * the event.
+ * @param aGUIEvent The handling event.
+ * @param aCapturingContent Capturing content if there is.
+ * nullptr, otherwise.
+ * @param aIsCapturingContentIgnored [out] true if aCapturingContent
+ * is not nullptr but it should be
+ * ignored to handle the event.
+ * @return A popup frame if there is a
+ * popup and we should handle the
+ * event in it. Otherwise,
+ * aRootFrameToHandleEvent.
+ * I.e., never returns nullptr.
+ */
+ nsIFrame* ComputeRootFrameToHandleEventWithPopup(
+ nsIFrame* aRootFrameToHandleEvent, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aCapturingContent, bool* aIsCapturingContentIgnored);
+
+ /**
+ * ComputeRootFrameToHandleEventWithCapturingContent() returns root frame
+ * to handle event for the capturing content, or aRootFrameToHandleEvent
+ * if it should be ignored.
+ *
+ * @param aRootFrameToHandleEvent Candidate root frame to handle
+ * the event.
+ * @param aCapturingContent Capturing content. nullptr is
+ * not allowed.
+ * @param aIsCapturingContentIgnored [out] true if aCapturingContent
+ * is not nullptr but it should be
+ * ignored to handle the event.
+ * @param aIsCaptureRetargeted [out] true if aCapturingContent
+ * is not nullptr but it's
+ * retargeted.
+ * @return A popup frame if there is a
+ * popup and we should handle the
+ * event in it. Otherwise,
+ * aRootFrameToHandleEvent.
+ * I.e., never returns nullptr.
+ */
+ nsIFrame* ComputeRootFrameToHandleEventWithCapturingContent(
+ nsIFrame* aRootFrameToHandleEvent, nsIContent* aCapturingContent,
+ bool* aIsCapturingContentIgnored, bool* aIsCaptureRetargeted);
+
+ /**
+ * HandleEventWithPointerCapturingContentWithoutItsFrame() handles
+ * aGUIEvent with aPointerCapturingContent when it does not have primary
+ * frame.
+ *
+ * @param aFrameForPresShell The frame for PresShell. See
+ * explanation of HandleEvent() for the
+ * details.
+ * @param aGUIEvent The handling event.
+ * @param aPointerCapturingContent Current pointer capturing content.
+ * Must not be nullptr.
+ * @param aEventStatus [in/out] The event status of aGUIEvent.
+ * @return Basically, result of
+ * HandeEventWithTraget().
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventWithPointerCapturingContentWithoutItsFrame(
+ nsIFrame* aFrameForPresShell, WidgetGUIEvent* aGUIEvent,
+ nsIContent* aPointerCapturingContent, nsEventStatus* aEventStatus);
+
+ /**
+ * HandleEventAtFocusedContent() handles aGUIEvent at focused content.
+ *
+ * @param aGUIEvent The handling event which should be handled at
+ * focused content.
+ * @param aEventStatus [in/out] The event status of aGUIEvent.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventAtFocusedContent(WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * ComputeFocusedEventTargetElement() returns event target element for
+ * aGUIEvent which should be handled with focused content.
+ * This may set/unset sLastKeyDownEventTarget if necessary.
+ *
+ * @param aGUIEvent The handling event.
+ * @return The element which should be the event
+ * target of aGUIEvent.
+ */
+ dom::Element* ComputeFocusedEventTargetElement(WidgetGUIEvent* aGUIEvent);
+
+ /**
+ * MaybeHandleEventWithAnotherPresShell() may handle aGUIEvent with another
+ * PresShell.
+ *
+ * @param aEventTargetElement The event target element of aGUIEvent.
+ * @param aGUIEvent Handling event.
+ * @param aEventStatus [in/out] EventStatus of aGUIEvent.
+ * @param aRv [out] Returns error if this gets an
+ * error handling the event.
+ * @return false if caller needs to keep handling
+ * the event by itself.
+ * true if caller shouldn't keep handling
+ * the event. Note that when no PresShell
+ * can handle the event, this returns true.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool MaybeHandleEventWithAnotherPresShell(dom::Element* aEventTargetElement,
+ WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus,
+ nsresult* aRv);
+
+ /**
+ * HandleRetargetedEvent() dispatches aGUIEvent on the PresShell without
+ * retargetting. This should be used only when caller computes final
+ * target of aGUIEvent.
+ *
+ * @param aGUIEvent Event to be dispatched.
+ * @param aEventStatus [in/out] EventStatus of aGUIEvent.
+ * @param aTarget The final target of aGUIEvent.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleRetargetedEvent(WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus,
+ nsIContent* aTarget) {
+ AutoCurrentEventInfoSetter eventInfoSetter(*this, nullptr, aTarget);
+ if (!mPresShell->GetCurrentEventFrame()) {
+ return NS_OK;
+ }
+ nsCOMPtr<nsIContent> overrideClickTarget;
+ return HandleEventWithCurrentEventInfo(aGUIEvent, aEventStatus, true,
+ overrideClickTarget);
+ }
+
+ /**
+ * HandleEventWithFrameForPresShell() handles aGUIEvent with the frame
+ * for mPresShell.
+ *
+ * @param aFrameForPresShell The frame for mPresShell.
+ * @param aGUIEvent The handling event. It shouldn't be
+ * handled with using coordinates nor
+ * handled at focused content.
+ * @param aEventStatus [in/out] The status of aGUIEvent.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventWithFrameForPresShell(nsIFrame* aFrameForPresShell,
+ WidgetGUIEvent* aGUIEvent,
+ nsEventStatus* aEventStatus);
+
+ /**
+ * HandleEventWithCurrentEventInfo() prepares to dispatch aEvent into the
+ * DOM, dispatches aEvent into the DOM with using current event info of
+ * mPresShell and notifies EventStateManager of that.
+ *
+ * @param aEvent Event to be dispatched.
+ * @param aEventStatus [in/out] EventStatus of aEvent.
+ * @param aIsHandlingNativeEvent true if aGUIEvent represents a native
+ * event.
+ * @param aOverrideClickTarget Override click event target.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ nsresult HandleEventWithCurrentEventInfo(WidgetEvent* aEvent,
+ nsEventStatus* aEventStatus,
+ bool aIsHandlingNativeEvent,
+ nsIContent* aOverrideClickTarget);
+
+ /**
+ * HandlingTimeAccumulator() may accumulate handling time of telemetry
+ * for each type of events.
+ */
+ class MOZ_STACK_CLASS HandlingTimeAccumulator final {
+ public:
+ HandlingTimeAccumulator() = delete;
+ HandlingTimeAccumulator(const HandlingTimeAccumulator& aOther) = delete;
+ HandlingTimeAccumulator(const EventHandler& aEventHandler,
+ const WidgetEvent* aEvent);
+ ~HandlingTimeAccumulator();
+
+ private:
+ const EventHandler& mEventHandler;
+ const WidgetEvent* mEvent;
+ TimeStamp mHandlingStartTime;
+ };
+
+ /**
+ * RecordEventPreparationPerformance() records event preparation performance
+ * with telemetry only when aEvent is a trusted event.
+ *
+ * @param aEvent The handling event which we've finished
+ * preparing something to dispatch.
+ */
+ void RecordEventPreparationPerformance(const WidgetEvent* aEvent);
+
+ /**
+ * RecordEventHandlingResponsePerformance() records event handling response
+ * performance with telemetry.
+ *
+ * @param aEvent The handled event.
+ */
+ void RecordEventHandlingResponsePerformance(const WidgetEvent* aEvent);
+
+ /**
+ * PrepareToDispatchEvent() prepares to dispatch aEvent.
+ *
+ * @param aEvent The handling event.
+ * @param aEventStatus [in/out] The status of aEvent.
+ * @param aTouchIsNew [out] Set to true if the event is an
+ * eTouchMove event and it represents new
+ * touch. Otherwise, set to false.
+ * @return true if the caller can dispatch the
+ * event into the DOM.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool PrepareToDispatchEvent(WidgetEvent* aEvent,
+ nsEventStatus* aEventStatus, bool* aTouchIsNew);
+
+ /**
+ * MaybeHandleKeyboardEventBeforeDispatch() may handle aKeyboardEvent
+ * if it should do something before dispatched into the DOM.
+ *
+ * @param aKeyboardEvent The handling keyboard event.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void MaybeHandleKeyboardEventBeforeDispatch(
+ WidgetKeyboardEvent* aKeyboardEvent);
+
+ /**
+ * This and the next two helper methods are used to target and position the
+ * context menu when the keyboard shortcut is used to open it.
+ *
+ * If another menu is open, the context menu is opened relative to the
+ * active menuitem within the menu, or the menu itself if no item is active.
+ * Otherwise, if the caret is visible, the menu is opened near the caret.
+ * Otherwise, if a selectable list such as a listbox is focused, the
+ * current item within the menu is opened relative to this item.
+ * Otherwise, the context menu is opened at the topleft corner of the
+ * view.
+ *
+ * Returns true if the context menu event should fire and false if it should
+ * not.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ bool AdjustContextMenuKeyEvent(WidgetMouseEvent* aMouseEvent);
+
+ MOZ_CAN_RUN_SCRIPT
+ bool PrepareToUseCaretPosition(nsIWidget* aEventWidget,
+ LayoutDeviceIntPoint& aTargetPt);
+
+ /**
+ * Get the selected item and coordinates in device pixels relative to root
+ * document's root view for element, first ensuring the element is onscreen.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void GetCurrentItemAndPositionForElement(dom::Element* aFocusedElement,
+ nsIContent** aTargetToUse,
+ LayoutDeviceIntPoint& aTargetPt,
+ nsIWidget* aRootWidget);
+
+ nsIContent* GetOverrideClickTarget(WidgetGUIEvent* aGUIEvent,
+ nsIFrame* aFrame);
+
+ /**
+ * DispatchEvent() tries to dispatch aEvent and notifies aEventStateManager
+ * of doing it.
+ *
+ * @param aEventStateManager EventStateManager which should handle
+ * the event before/after dispatching
+ * aEvent into the DOM.
+ * @param aEvent The handling event.
+ * @param aTouchIsNew Set this to true when the message is
+ * eTouchMove and it's newly touched.
+ * Then, the "touchmove" event becomes
+ * cancelable.
+ * @param aEventStatus [in/out] The status of aEvent.
+ * @param aOverrideClickTarget Override click event target.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ DispatchEvent(EventStateManager* aEventStateManager, WidgetEvent* aEvent,
+ bool aTouchIsNew, nsEventStatus* aEventStatus,
+ nsIContent* aOverrideClickTarget);
+
+ /**
+ * DispatchEventToDOM() actually dispatches aEvent into the DOM tree.
+ *
+ * @param aEvent Event to be dispatched into the DOM tree.
+ * @param aEventStatus [in/out] EventStatus of aEvent.
+ * @param aEventCB The callback kicked when the event moves
+ * from the default group to the system group.
+ */
+ MOZ_CAN_RUN_SCRIPT nsresult
+ DispatchEventToDOM(WidgetEvent* aEvent, nsEventStatus* aEventStatus,
+ nsPresShellEventCB* aEventCB);
+
+ /**
+ * DispatchTouchEventToDOM() dispatches touch events into the DOM tree.
+ *
+ * @param aEvent The source of events to be dispatched into the
+ * DOM tree.
+ * @param aEventStatus [in/out] EventStatus of aEvent.
+ * @param aEventCB The callback kicked when the events move
+ * from the default group to the system group.
+ * @param aTouchIsNew Set this to true when the message is eTouchMove
+ * and it's newly touched. Then, the "touchmove"
+ * event becomes cancelable.
+ */
+ MOZ_CAN_RUN_SCRIPT void DispatchTouchEventToDOM(
+ WidgetEvent* aEvent, nsEventStatus* aEventStatus,
+ nsPresShellEventCB* aEventCB, bool aTouchIsNew);
+
+ /**
+ * FinalizeHandlingEvent() should be called after calling DispatchEvent()
+ * and then, this cleans up the state of mPresShell and aEvent.
+ *
+ * @param aEvent The handled event.
+ */
+ MOZ_CAN_RUN_SCRIPT void FinalizeHandlingEvent(WidgetEvent* aEvent);
+
+ /**
+ * AutoCurrentEventInfoSetter() pushes and pops current event info of
+ * aEventHandler.mPresShell.
+ */
+ struct MOZ_STACK_CLASS AutoCurrentEventInfoSetter final {
+ explicit AutoCurrentEventInfoSetter(EventHandler& aEventHandler)
+ : mEventHandler(aEventHandler) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEventHandler.mCurrentEventInfoSetter);
+ mEventHandler.mCurrentEventInfoSetter = this;
+ mEventHandler.mPresShell->PushCurrentEventInfo(nullptr, nullptr);
+ }
+ AutoCurrentEventInfoSetter(EventHandler& aEventHandler, nsIFrame* aFrame,
+ nsIContent* aContent)
+ : mEventHandler(aEventHandler) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEventHandler.mCurrentEventInfoSetter);
+ mEventHandler.mCurrentEventInfoSetter = this;
+ mEventHandler.mPresShell->PushCurrentEventInfo(aFrame, aContent);
+ }
+ AutoCurrentEventInfoSetter(EventHandler& aEventHandler,
+ EventTargetData& aEventTargetData)
+ : mEventHandler(aEventHandler) {
+ MOZ_DIAGNOSTIC_ASSERT(!mEventHandler.mCurrentEventInfoSetter);
+ mEventHandler.mCurrentEventInfoSetter = this;
+ mEventHandler.mPresShell->PushCurrentEventInfo(
+ aEventTargetData.GetFrame(), aEventTargetData.GetContent());
+ }
+ ~AutoCurrentEventInfoSetter() {
+ mEventHandler.mPresShell->PopCurrentEventInfo();
+ mEventHandler.mCurrentEventInfoSetter = nullptr;
+ }
+
+ private:
+ EventHandler& mEventHandler;
+ };
+
+ /**
+ * Wrapper methods to access methods of mPresShell.
+ */
+ nsPresContext* GetPresContext() const {
+ return mPresShell->GetPresContext();
+ }
+ Document* GetDocument() const { return mPresShell->GetDocument(); }
+ nsCSSFrameConstructor* FrameConstructor() const {
+ return mPresShell->FrameConstructor();
+ }
+ already_AddRefed<nsPIDOMWindowOuter> GetFocusedDOMWindowInOurWindow() {
+ return mPresShell->GetFocusedDOMWindowInOurWindow();
+ }
+ already_AddRefed<PresShell> GetParentPresShellForEventHandling() {
+ return mPresShell->GetParentPresShellForEventHandling();
+ }
+ OwningNonNull<PresShell> mPresShell;
+ AutoCurrentEventInfoSetter* mCurrentEventInfoSetter;
+ static TimeStamp sLastInputCreated;
+ static TimeStamp sLastInputProcessed;
+ static StaticRefPtr<dom::Element> sLastKeyDownEventTargetElement;
+ };
+
+ PresShell* GetRootPresShell() const;
+
+ bool IsTransparentContainerElement() const;
+ ColorScheme DefaultBackgroundColorScheme() const;
+ nscolor GetDefaultBackgroundColorToDraw() const;
+
+ //////////////////////////////////////////////////////////////////////////////
+ // Approximate frame visibility tracking implementation.
+ //////////////////////////////////////////////////////////////////////////////
+
+ void UpdateApproximateFrameVisibility();
+ void DoUpdateApproximateFrameVisibility(bool aRemoveOnly);
+
+ void ClearApproximatelyVisibleFramesList(
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+ static void ClearApproximateFrameVisibilityVisited(nsView* aView,
+ bool aClear);
+ static void MarkFramesInListApproximatelyVisible(const nsDisplayList& aList);
+ void MarkFramesInSubtreeApproximatelyVisible(nsIFrame* aFrame,
+ const nsRect& aRect,
+ bool aRemoveOnly = false);
+
+ void DecApproximateVisibleCount(
+ VisibleFrames& aFrames,
+ const Maybe<OnNonvisible>& aNonvisibleAction = Nothing());
+
+ nsRevocableEventPtr<nsRunnableMethod<PresShell>>
+ mUpdateApproximateFrameVisibilityEvent;
+
+ // A set of frames that were visible or could be visible soon at the time
+ // that we last did an approximate frame visibility update.
+ VisibleFrames mApproximatelyVisibleFrames;
+
+#ifdef DEBUG
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY bool VerifyIncrementalReflow();
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void DoVerifyReflow();
+ void VerifyHasDirtyRootAncestor(nsIFrame* aFrame);
+
+ bool mInVerifyReflow = false;
+ // The reflow root under which we're currently reflowing. Null when
+ // not in reflow.
+ nsIFrame* mCurrentReflowRoot = nullptr;
+#endif // #ifdef DEBUG
+
+ // Send, and reset, the current per tick telemetry. This includes:
+ // * non-zero number of style and layout flushes
+ // * non-zero ms duration spent in style and reflow since the last tick.
+ void PingPerTickTelemetry(FlushType aFlushType);
+
+ private:
+ // IMPORTANT: The ownership implicit in the following member variables
+ // has been explicitly checked. If you add any members to this class,
+ // please make the ownership explicit (pinkerton, scc).
+
+ // These are the same Document and PresContext owned by the DocViewer.
+ // we must share ownership.
+ // mDocument and mPresContext should've never been cleared nor swapped with
+ // another instance while PresShell instance is alive so that it's safe to
+ // call their can-run- script methods without local RefPtr variables.
+ MOZ_KNOWN_LIVE RefPtr<Document> const mDocument;
+ MOZ_KNOWN_LIVE RefPtr<nsPresContext> const mPresContext;
+ UniquePtr<nsCSSFrameConstructor> mFrameConstructor;
+ nsViewManager* mViewManager; // [WEAK] docViewer owns it so I don't have to
+ RefPtr<nsFrameSelection> mSelection;
+ // The frame selection that last took focus on this shell, which we need to
+ // hide if we focus another selection. May or may not be the same as
+ // `mSelection`.
+ RefPtr<nsFrameSelection> mFocusedFrameSelection;
+ RefPtr<nsCaret> mCaret;
+ RefPtr<nsCaret> mOriginalCaret;
+ RefPtr<AccessibleCaretEventHub> mAccessibleCaretEventHub;
+ // Pointer into mFrameConstructor - this is purely so that GetRootFrame() can
+ // be inlined:
+ nsFrameManager* mFrameManager;
+ WeakPtr<nsDocShell> mForwardingContainer;
+
+ // The `performance.now()` value when we last started to process reflows.
+ DOMHighResTimeStamp mLastReflowStart{0.0};
+
+ // At least on Win32 and Mac after interupting a reflow we need to post
+ // the resume reflow event off a timer to avoid event starvation because
+ // posted messages are processed before other messages when the modal
+ // moving/sizing loop is running, see bug 491700 for details.
+ nsCOMPtr<nsITimer> mReflowContinueTimer;
+
+#ifdef DEBUG
+ // We track allocated pointers in a diagnostic hash set, to assert against
+ // missing/double frees. This set is allocated infallibly in the PresShell
+ // constructor's initialization list. The set can get quite large, so we use
+ // fallible allocation when inserting into it; and if these operations ever
+ // fail, then we just get rid of the set and stop using this diagnostic from
+ // that point on. (There's not much else we can do, when the set grows
+ // larger than the available memory.)
+ UniquePtr<nsTHashSet<void*>> mAllocatedPointers{
+ MakeUnique<nsTHashSet<void*>>()};
+#endif
+
+ // A list of stack weak frames. This is a pointer to the last item in the
+ // list.
+ AutoWeakFrame* mAutoWeakFrames;
+
+ // A hash table of heap allocated weak frames.
+ nsTHashSet<WeakFrame*> mWeakFrames;
+
+ // Reflow roots that need to be reflowed.
+ DepthOrderedFrameList mDirtyRoots;
+
+ // These two fields capture call stacks of any changes that require a restyle
+ // or a reflow. Only the first change per restyle / reflow is recorded (the
+ // one that caused a call to SetNeedStyleFlush() / SetNeedLayoutFlush()).
+ UniquePtr<ProfileChunkedBuffer> mStyleCause;
+ UniquePtr<ProfileChunkedBuffer> mReflowCause;
+
+ nsTArray<UniquePtr<DelayedEvent>> mDelayedEvents;
+
+ nsRevocableEventPtr<nsSynthMouseMoveEvent> mSynthMouseMoveEvent;
+
+ TouchManager mTouchManager;
+
+ RefPtr<ZoomConstraintsClient> mZoomConstraintsClient;
+ RefPtr<GeckoMVMContext> mMVMContext;
+ RefPtr<MobileViewportManager> mMobileViewportManager;
+
+ // This timer controls painting suppression. Until it fires
+ // or all frames are constructed, we won't paint anything but
+ // our <body> background and scrollbars.
+ nsCOMPtr<nsITimer> mPaintSuppressionTimer;
+
+ // Information about live content (which still stay in DOM tree).
+ // Used in case we need re-dispatch event after sending pointer event,
+ // when target of pointer event was deleted during executing user handlers.
+ nsCOMPtr<nsIContent> mPointerEventTarget;
+
+ nsCOMPtr<nsIContent> mLastAnchorScrolledTo;
+
+ // Information needed to properly handle scrolling content into view if the
+ // pre-scroll reflow flush can be interrupted. mContentToScrollTo is non-null
+ // between the initial scroll attempt and the first time we finish processing
+ // all our dirty roots. mContentToScrollTo has a content property storing the
+ // details for the scroll operation, see ScrollIntoViewData above.
+ nsCOMPtr<nsIContent> mContentToScrollTo;
+
+#ifdef ACCESSIBILITY
+ a11y::DocAccessible* mDocAccessible;
+#endif // #ifdef ACCESSIBILITY
+
+ nsIFrame* mCurrentEventFrame;
+ nsCOMPtr<nsIContent> mCurrentEventContent;
+ nsTArray<nsIFrame*> mCurrentEventFrameStack;
+ nsCOMArray<nsIContent> mCurrentEventContentStack;
+ // Set of frames that we should mark with NS_FRAME_HAS_DIRTY_CHILDREN after
+ // we finish reflowing mCurrentReflowRoot.
+ nsTHashSet<nsIFrame*> mFramesToDirty;
+ nsTHashSet<nsIScrollableFrame*> mPendingScrollAnchorSelection;
+ nsTHashSet<nsIScrollableFrame*> mPendingScrollAnchorAdjustment;
+ nsTHashSet<nsIScrollableFrame*> mPendingScrollResnap;
+
+ nsTHashSet<nsIContent*> mHiddenContentInForcedLayout;
+
+ nsTHashSet<nsIFrame*> mContentVisibilityAutoFrames;
+
+ // The type of content relevancy to update the next time content relevancy
+ // updates are triggered for `content-visibility: auto` frames.
+ ContentRelevancy mContentVisibilityRelevancyToUpdate;
+
+ nsCallbackEventRequest* mFirstCallbackEventRequest = nullptr;
+ nsCallbackEventRequest* mLastCallbackEventRequest = nullptr;
+
+ // This is used for synthetic mouse events that are sent when what is under
+ // the mouse pointer may have changed without the mouse moving (eg scrolling,
+ // change to the document contents).
+ // It is set only on a presshell for a root document, this value represents
+ // the last observed location of the mouse relative to that root document,
+ // in visual coordinates. It is set to (NS_UNCONSTRAINEDSIZE,
+ // NS_UNCONSTRAINEDSIZE) if the mouse isn't over our window or there is no
+ // last observed mouse location for some reason.
+ nsPoint mMouseLocation;
+ // This is used for the synthetic mouse events too. This is set when a mouse
+ // event is dispatched into the DOM.
+ static int16_t sMouseButtons;
+ // The last observed pointer location relative to that root document in visual
+ // coordinates.
+ nsPoint mLastOverWindowPointerLocation;
+ // This is an APZ state variable that tracks the target guid for the last
+ // mouse event that was processed (corresponding to mMouseLocation). This is
+ // needed for the synthetic mouse events.
+ layers::ScrollableLayerGuid mMouseEventTargetGuid;
+
+ // Only populated on root content documents.
+ nsSize mVisualViewportSize;
+
+ // The focus information needed for async keyboard scrolling
+ FocusTarget mAPZFocusTarget;
+
+ using Arena = nsPresArena<8192, ArenaObjectID, eArenaObjectID_COUNT>;
+ Arena mFrameArena;
+
+ Maybe<nsPoint> mVisualViewportOffset;
+
+ // A pending visual scroll offset that we will ask APZ to scroll to
+ // during the next transaction. Cleared when we send the transaction.
+ // Only applicable to the RCD pres shell.
+ Maybe<VisualScrollUpdate> mPendingVisualScrollUpdate;
+
+ // Used to force allocation and rendering of proportionally more or
+ // less pixels in both dimensions.
+ Maybe<float> mResolution;
+ ResolutionChangeOrigin mLastResolutionChangeOrigin;
+
+ TimeStamp mLoadBegin; // used to time loads
+
+ TimeStamp mLastOSWake;
+
+ // Count of the number of times this presshell has been painted to a window.
+ uint64_t mPaintCount;
+
+ // The focus sequence number of the last processed input event
+ uint64_t mAPZFocusSequenceNumber;
+
+ nscoord mLastAnchorScrollPositionY = 0;
+
+ // Most recent canvas background color.
+ CanvasBackground mCanvasBackground;
+
+ int32_t mActiveSuppressDisplayport;
+
+ uint32_t mPresShellId;
+
+ // Cached font inflation values. This is done to prevent changing of font
+ // inflation until a page is reloaded.
+ uint32_t mFontSizeInflationEmPerLine;
+ uint32_t mFontSizeInflationMinTwips;
+ uint32_t mFontSizeInflationLineThreshold;
+
+ // Can be multiple of nsISelectionDisplay::DISPLAY_*.
+ int16_t mSelectionFlags;
+
+ // This is used to protect ourselves from triggering reflow while in the
+ // middle of frame construction and the like... it really shouldn't be
+ // needed, one hopes, but it is for now.
+ uint16_t mChangeNestCount;
+
+ // Flags controlling how our document is rendered. These persist
+ // between paints and so are tied with retained layer pixels.
+ // PresShell flushes retained layers when the rendering state
+ // changes in a way that prevents us from being able to (usefully)
+ // re-use old pixels.
+ RenderingStateFlags mRenderingStateFlags;
+
+ // Whether we're currently under a FlushPendingNotifications.
+ // This is used to handle flush reentry correctly.
+ // NOTE: This can't be a bitfield since AutoRestore has a reference to this
+ // variable.
+ bool mInFlush;
+
+ bool mCaretEnabled : 1;
+
+ // True if a layout flush might not be a no-op
+ bool mNeedLayoutFlush : 1;
+
+ // True if a style flush might not be a no-op
+ bool mNeedStyleFlush : 1;
+
+ // True if there are throttled animations that would be processed when
+ // performing a flush with mFlushAnimations == true.
+ bool mNeedThrottledAnimationFlush : 1;
+
+ bool mVisualViewportSizeSet : 1;
+
+ bool mDidInitialize : 1;
+ bool mIsDestroying : 1;
+ bool mIsReflowing : 1;
+ bool mIsObservingDocument : 1;
+
+ // Whether we shouldn't ever get to FlushPendingNotifications. This flag is
+ // meant only to sanity-check / assert that FlushPendingNotifications doesn't
+ // happen during certain periods of time. It shouldn't be made public nor used
+ // for other purposes.
+ bool mForbiddenToFlush : 1;
+
+ // We've been disconnected from the document. We will refuse to paint the
+ // document until either our timer fires or all frames are constructed.
+ bool mIsDocumentGone : 1;
+ bool mHaveShutDown : 1;
+
+ // For all documents we initially lock down painting.
+ bool mPaintingSuppressed : 1;
+
+ bool mLastRootReflowHadUnconstrainedBSize : 1;
+
+ // Indicates that it is safe to unlock painting once all pending reflows
+ // have been processed.
+ bool mShouldUnsuppressPainting : 1;
+
+ bool mIgnoreFrameDestruction : 1;
+
+ bool mIsActive : 1;
+ bool mFrozen : 1;
+ bool mIsFirstPaint : 1;
+ bool mObservesMutationsForPrint : 1;
+
+ // Whether the most recent interruptible reflow was actually interrupted:
+ bool mWasLastReflowInterrupted : 1;
+
+ // True if we're observing the refresh driver for style flushes.
+ bool mObservingStyleFlushes : 1;
+
+ // True if we're observing the refresh driver for layout flushes, that is, if
+ // we have a reflow scheduled.
+ //
+ // Guaranteed to be false if mReflowContinueTimer is non-null.
+ bool mObservingLayoutFlushes : 1;
+
+ bool mResizeEventPending : 1;
+
+ bool mFontSizeInflationForceEnabled : 1;
+ bool mFontSizeInflationDisabledInMasterProcess : 1;
+ bool mFontSizeInflationEnabled : 1;
+
+ // If a document belongs to an invisible DocShell, this flag must be set
+ // to true, so we can avoid any paint calls for widget related to this
+ // presshell.
+ bool mIsNeverPainting : 1;
+
+ // Whether the most recent change to the pres shell resolution was
+ // originated by the main thread.
+ bool mResolutionUpdated : 1;
+
+ // True if the resolution has been ever changed by APZ.
+ bool mResolutionUpdatedByApz : 1;
+
+ // Whether this presshell is hidden by 'vibility:hidden' on an ancestor
+ // nsSubDocumentFrame.
+ bool mUnderHiddenEmbedderElement : 1;
+
+ bool mDocumentLoading : 1;
+ bool mNoDelayedMouseEvents : 1;
+ bool mNoDelayedKeyEvents : 1;
+
+ bool mApproximateFrameVisibilityVisited : 1;
+
+ // Whether the last chrome-only escape key event is consumed.
+ bool mIsLastChromeOnlyEscapeKeyConsumed : 1;
+
+ // Whether the widget has received a paint message yet.
+ bool mHasReceivedPaintMessage : 1;
+
+ bool mIsLastKeyDownCanceled : 1;
+
+ // Whether we have ever handled a user input event
+ bool mHasHandledUserInput : 1;
+
+ // Whether we should dispatch keypress events even for non-printable keys
+ // for keeping backward compatibility.
+ bool mForceDispatchKeyPressEventsForNonPrintableKeys : 1;
+ // Whether we should set keyCode or charCode value of keypress events whose
+ // value is zero to the other value or not. When this is set to true, we
+ // should keep using legacy keyCode and charCode values (i.e., one of them
+ // is always 0).
+ bool mForceUseLegacyKeyCodeAndCharCodeValues : 1;
+ // Whether mForceDispatchKeyPressEventsForNonPrintableKeys and
+ // mForceUseLegacyKeyCodeAndCharCodeValues are initialized.
+ bool mInitializedWithKeyPressEventDispatchingBlacklist : 1;
+
+ // Set to true if mMouseLocation is set by a mouse event which is synthesized
+ // for tests.
+ bool mMouseLocationWasSetBySynthesizedMouseEventForTests : 1;
+
+ bool mHasTriedFastUnsuppress : 1;
+
+ bool mProcessingReflowCommands : 1;
+ bool mPendingDidDoReflow : 1;
+
+ struct CapturingContentInfo final {
+ CapturingContentInfo()
+ : mRemoteTarget(nullptr),
+ mAllowed(false),
+ mPointerLock(false),
+ mRetargetToElement(false),
+ mPreventDrag(false) {}
+
+ // capture should only be allowed during a mousedown event
+ StaticRefPtr<nsIContent> mContent;
+ dom::BrowserParent* mRemoteTarget;
+ bool mAllowed;
+ bool mPointerLock;
+ bool mRetargetToElement;
+ bool mPreventDrag;
+ };
+ static CapturingContentInfo sCapturingContentInfo;
+
+ static bool sDisableNonTestMouseEvents;
+
+ static bool sProcessInteractable;
+
+ layout_telemetry::Data mLayoutTelemetry;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(PresShell, NS_PRESSHELL_IID)
+
+} // namespace mozilla
+
+#endif // mozilla_PresShell_h
diff --git a/layout/base/PresShellForwards.h b/layout/base/PresShellForwards.h
new file mode 100644
index 0000000000..f1f81d4c33
--- /dev/null
+++ b/layout/base/PresShellForwards.h
@@ -0,0 +1,245 @@
+/* -*- 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_PresShellForwards_h
+#define mozilla_PresShellForwards_h
+
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Maybe.h"
+
+struct CapturingContentInfo;
+
+namespace mozilla {
+
+class PresShell;
+
+// Flags to pass to PresShell::SetCapturingContent().
+enum class CaptureFlags {
+ None = 0,
+ // When assigning capture, ignore whether capture is allowed or not.
+ IgnoreAllowedState = 1 << 0,
+ // Set if events should be targeted at the capturing content or its children.
+ RetargetToElement = 1 << 1,
+ // Set if the current capture wants drags to be prevented.
+ PreventDragStart = 1 << 2,
+ // Set when the mouse is pointer locked, and events are sent to locked
+ // element.
+ PointerLock = 1 << 3,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CaptureFlags)
+
+enum class ResizeReflowOptions : uint32_t {
+ NoOption = 0,
+ // the resulting BSize can be less than the given one, producing
+ // shrink-to-fit sizing in the block dimension
+ BSizeLimit = 1 << 0,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ResizeReflowOptions)
+
+enum class IntrinsicDirty {
+ // Don't mark any intrinsic inline sizes dirty.
+ None,
+ // Mark intrinsic inline sizes dirty on aFrame and its ancestors.
+ FrameAndAncestors,
+ // Mark intrinsic inline sizes dirty on aFrame, its ancestors, and its
+ // descendants.
+ FrameAncestorsAndDescendants,
+};
+
+enum class ReflowRootHandling {
+ PositionOrSizeChange, // aFrame is changing position or size
+ NoPositionOrSizeChange, // ... NOT changing ...
+ InferFromBitToAdd, // is changing iff (aBitToAdd == NS_FRAME_IS_DIRTY)
+
+ // Note: With IntrinsicDirty::FrameAncestorsAndDescendants, these can also
+ // apply to out-of-flows in addition to aFrame.
+};
+
+// Indicates where to scroll on a given axis.
+struct WhereToScroll {
+ // The percentage of the scroll axis that we're scrolling to.
+ // Nothing() represents "scroll to nearest".
+ Maybe<int16_t> mPercentage;
+
+ // Default is nearest.
+ constexpr WhereToScroll() = default;
+
+ explicit constexpr WhereToScroll(int16_t aPercentage)
+ : mPercentage(Some(aPercentage)) {}
+
+ enum { Nearest };
+ MOZ_IMPLICIT constexpr WhereToScroll(decltype(Nearest)) : WhereToScroll() {}
+ enum { Start };
+ MOZ_IMPLICIT constexpr WhereToScroll(decltype(Start)) : WhereToScroll(0) {}
+ enum { Center };
+ MOZ_IMPLICIT constexpr WhereToScroll(decltype(Center)) : WhereToScroll(50) {}
+ enum { End };
+ MOZ_IMPLICIT constexpr WhereToScroll(decltype(End)) : WhereToScroll(100) {}
+};
+
+// See the comment for constructor of ScrollAxis for the detail.
+enum class WhenToScroll : uint8_t {
+ Always,
+ IfNotVisible,
+ IfNotFullyVisible,
+};
+
+struct ScrollAxis final {
+ /**
+ * aWhere:
+ * Either a percentage or a special value. PresShell defines:
+ * * (Default) kScrollMinimum = -1: The visible area is scrolled the
+ * minimum amount to show as much as possible of the frame. This won't
+ * hide any initially visible part of the frame.
+ * * kScrollToTop = 0: The frame's upper edge is aligned with the top edge
+ * of the visible area.
+ * * kScrollToBottom = 100: The frame's bottom edge is aligned with the
+ * bottom edge of the visible area.
+ * * kScrollToLeft = 0: The frame's left edge is aligned with the left edge
+ * of the visible area.
+ * * kScrollToRight = 100: The frame's right edge is aligned* with the right
+ * edge of the visible area.
+ * * kScrollToCenter = 50: The frame is centered along the axis the
+ * ScrollAxis is used for.
+ *
+ * Other values are treated as a percentage, and the point*"percent"
+ * down the frame is placed at the point "percent" down the visible area.
+ *
+ * aWhen:
+ * * (Default) WhenToScroll::IfNotFullyVisible: Move the frame only if it is
+ * not fully visible (including if it's not visible at all). Note that
+ * in this case if the frame is too large to fit in view, it will only
+ * be scrolled if more of it can fit than is already in view.
+ * * WhenToScroll::IfNotVisible: Move the frame only if none of it is
+ * visible.
+ * * WhenToScroll::Always: Move the frame regardless of its current
+ * visibility.
+ *
+ * aOnlyIfPerceivedScrollableDirection:
+ * If the direction is not a perceived scrollable direction (i.e. no
+ * scrollbar showing and less than one device pixel of scrollable
+ * distance), don't scroll. Defaults to false.
+ */
+ explicit ScrollAxis(WhereToScroll aWhere = WhereToScroll::Nearest,
+ WhenToScroll aWhen = WhenToScroll::IfNotFullyVisible,
+ bool aOnlyIfPerceivedScrollableDirection = false)
+ : mWhereToScroll(aWhere),
+ mWhenToScroll(aWhen),
+ mOnlyIfPerceivedScrollableDirection(
+ aOnlyIfPerceivedScrollableDirection) {}
+
+ WhereToScroll mWhereToScroll;
+ WhenToScroll mWhenToScroll;
+ bool mOnlyIfPerceivedScrollableDirection : 1;
+};
+
+enum class ScrollFlags {
+ None = 0,
+ ScrollFirstAncestorOnly = 1 << 0,
+ ScrollOverflowHidden = 1 << 1,
+ ScrollNoParentFrames = 1 << 2,
+ ScrollSmooth = 1 << 3,
+ ScrollSmoothAuto = 1 << 4,
+ TriggeredByScript = 1 << 5,
+ // ScrollOverflowHidden | ScrollNoParentFrames
+ AnchorScrollFlags = (1 << 1) | (1 << 2),
+ ALL_BITS = (1 << 6) - 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ScrollFlags)
+
+// See comment at declaration of RenderDocument() for the detail.
+enum class RenderDocumentFlags {
+ None = 0,
+ IsUntrusted = 1 << 0,
+ IgnoreViewportScrolling = 1 << 1,
+ DrawCaret = 1 << 2,
+ UseWidgetLayers = 1 << 3,
+ AsyncDecodeImages = 1 << 4,
+ DocumentRelative = 1 << 5,
+ DrawWindowNotFlushing = 1 << 6,
+ UseHighQualityScaling = 1 << 7,
+ ResetViewportScrolling = 1 << 8,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RenderDocumentFlags)
+
+// See comment at declaration of RenderSelection() for the detail.
+enum class RenderImageFlags {
+ None = 0,
+ IsImage = 1 << 0,
+ AutoScale = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RenderImageFlags)
+
+enum class ResolutionChangeOrigin : uint8_t {
+ Apz,
+ Test,
+ MainThreadRestore,
+ MainThreadAdjustment,
+};
+
+enum class PaintFlags {
+ None = 0,
+ /* Sync-decode images. */
+ PaintSyncDecodeImages = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintFlags)
+
+enum class PaintInternalFlags {
+ None = 0,
+ /* Sync-decode images. */
+ PaintSyncDecodeImages = 1 << 1,
+ /* Composite layers to the window. */
+ PaintComposite = 1 << 2,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PaintInternalFlags)
+
+// This is a private enum class of PresShell, but currently,
+// MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS isn't available in class definition.
+// Therefore, we need to put this here.
+enum class RenderingStateFlags : uint8_t {
+ None = 0,
+ IgnoringViewportScrolling = 1 << 0,
+ DrawWindowNotFlushing = 1 << 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(RenderingStateFlags)
+
+// The state of the dynamic toolbar on Mobile.
+enum class DynamicToolbarState {
+ None, // No dynamic toolbar, i.e. the toolbar is static or there is
+ // no available toolbar.
+ Expanded, // The dynamic toolbar is expanded to the maximum height.
+ InTransition, // The dynamic toolbar is being shown/hidden.
+ Collapsed, // The dynamic toolbar is collapsed to zero height.
+};
+
+#ifdef DEBUG
+
+enum class VerifyReflowFlags {
+ None = 0,
+ On = 1 << 0,
+ Noisy = 1 << 1,
+ All = 1 << 2,
+ DumpCommands = 1 << 3,
+ NoisyCommands = 1 << 4,
+ ReallyNoisyCommands = 1 << 5,
+ DuringResizeReflow = 1 << 6,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(VerifyReflowFlags)
+
+#endif // #ifdef DEBUG
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_PresShellForwards_h
diff --git a/layout/base/PresShellInlines.h b/layout/base/PresShellInlines.h
new file mode 100644
index 0000000000..c04c3b54a9
--- /dev/null
+++ b/layout/base/PresShellInlines.h
@@ -0,0 +1,83 @@
+/* -*- 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_PresShellInlines_h
+#define mozilla_PresShellInlines_h
+
+#include "nsDocShell.h"
+#include "GeckoProfiler.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Element.h"
+
+namespace mozilla {
+
+void PresShell::SetNeedLayoutFlush() {
+ mNeedLayoutFlush = true;
+ if (dom::Document* doc = mDocument->GetDisplayDocument()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->mNeedLayoutFlush = true;
+ }
+ }
+
+ if (!mReflowCause) {
+ mReflowCause = profiler_capture_backtrace();
+ }
+
+ mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
+}
+
+void PresShell::SetNeedStyleFlush() {
+ mNeedStyleFlush = true;
+ PROFILER_MARKER_UNTYPED(
+ "SetNeedStyleFlush", LAYOUT,
+ MarkerOptions(MarkerStack::Capture(StackCaptureOptions::NonNative),
+ mPresContext ? MarkerInnerWindowIdFromDocShell(
+ mPresContext->GetDocShell())
+ : MarkerInnerWindowId::NoId()));
+
+ if (dom::Document* doc = mDocument->GetDisplayDocument()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->mNeedStyleFlush = true;
+ }
+ }
+
+ if (!mStyleCause) {
+ mStyleCause = profiler_capture_backtrace();
+ }
+
+ mLayoutTelemetry.IncReqsPerFlush(FlushType::Layout);
+}
+
+void PresShell::EnsureStyleFlush() {
+ SetNeedStyleFlush();
+ ObserveStyleFlushes();
+}
+
+void PresShell::SetNeedThrottledAnimationFlush() {
+ mNeedThrottledAnimationFlush = true;
+ if (dom::Document* doc = mDocument->GetDisplayDocument()) {
+ if (PresShell* presShell = doc->GetPresShell()) {
+ presShell->mNeedThrottledAnimationFlush = true;
+ }
+ }
+}
+
+ServoStyleSet* PresShell::StyleSet() const {
+ return mDocument->StyleSetForPresShell();
+}
+
+/* static */
+inline void PresShell::EventHandler::OnPresShellDestroy(Document* aDocument) {
+ if (sLastKeyDownEventTargetElement &&
+ sLastKeyDownEventTargetElement->OwnerDoc() == aDocument) {
+ sLastKeyDownEventTargetElement = nullptr;
+ }
+}
+
+} // namespace mozilla
+
+#endif // mozilla_PresShellInlines_h
diff --git a/layout/base/PresState.ipdlh b/layout/base/PresState.ipdlh
new file mode 100644
index 0000000000..b04d7ed5f0
--- /dev/null
+++ b/layout/base/PresState.ipdlh
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */
+/* 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 "mozilla/GfxMessageUtils.h";
+include "mozilla/dom/IPCBlobUtils.h";
+
+using struct mozilla::void_t from "mozilla/ipc/IPCCore.h";
+using struct nsPoint from "nsPoint.h";
+[RefCounted] using class mozilla::dom::BlobImpl from "mozilla/dom/BlobImpl.h";
+
+include CustomElementTypes;
+
+namespace mozilla {
+
+struct SelectContentData {
+ uint32_t[] indices;
+ nsString[] values;
+};
+
+struct CheckedContentData {
+ bool checked;
+};
+
+union FileContentData {
+ nullable BlobImpl;
+ nsString;
+};
+
+struct TextContentData {
+ nsString value;
+ bool lastValueChangeWasInteractive;
+};
+
+union PresContentData {
+ void_t;
+ TextContentData;
+ SelectContentData;
+ CheckedContentData;
+ // We can need to serialize blobs in order to transmit this type, so we need
+ // to handle that in a custom handler.
+ FileContentData[];
+ CustomElementTuple;
+};
+
+struct PresState {
+ PresContentData contentData;
+ // For frames where layout and visual viewport aren't one and the same thing,
+ // scrollState will store the position of the *visual* viewport.
+ nsPoint scrollState;
+ bool allowScrollOriginDowngrade;
+ float resolution;
+ bool disabledSet;
+ bool disabled;
+ bool droppedDown;
+};
+
+} // namespace mozilla
diff --git a/layout/base/RelativeTo.h b/layout/base/RelativeTo.h
new file mode 100644
index 0000000000..e82802e2e8
--- /dev/null
+++ b/layout/base/RelativeTo.h
@@ -0,0 +1,51 @@
+/* -*- 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_RelativeTo_h
+#define mozilla_RelativeTo_h
+
+#include <ostream>
+
+class nsIFrame;
+
+namespace mozilla {
+
+// A flag that can be used to annotate a frame to distinguish coordinates
+// relative to the viewport frame as being in layout or visual coordinates.
+enum class ViewportType { Layout, Visual };
+
+// A struct that combines a frame with a ViewportType annotation. The
+// combination completely describes what a set of coordinates is "relative to".
+// Notes on expected usage:
+// - The boundary between visual and layout coordinates is approximately
+// at the root content document (RCD)'s ViewportFrame, which we'll
+// call "RCD-VF".
+// - Coordinates relative to the RCD-VF's descendants (other than the
+// RCD's viewport scrollbar frames) should be in layout coordinates.
+// - Coordinates relative to the RCD-VF's ancestors should be in visual
+// coordinates (note that in an e10s setup, the RCD-VF doesn't
+// typically have in-process ancestors).
+// - Coordinates relative to the RCD-VF itself can be in either layout
+// or visual coordinates.
+struct RelativeTo {
+ const nsIFrame* mFrame = nullptr;
+ // Choose ViewportType::Layout as the default as this is what the vast
+ // majority of layout code deals with.
+ ViewportType mViewportType = ViewportType::Layout;
+ bool operator==(const RelativeTo& aOther) const {
+ return mFrame == aOther.mFrame && mViewportType == aOther.mViewportType;
+ }
+ friend std::ostream& operator<<(std::ostream& aOs, const RelativeTo& aR) {
+ return aOs << "{" << aR.mFrame << ", "
+ << (aR.mViewportType == ViewportType::Visual ? "visual"
+ : "layout")
+ << "}";
+ }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_RelativeTo_h
diff --git a/layout/base/RestyleManager.cpp b/layout/base/RestyleManager.cpp
new file mode 100644
index 0000000000..9c313a254c
--- /dev/null
+++ b/layout/base/RestyleManager.cpp
@@ -0,0 +1,3881 @@
+/* -*- 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 "mozilla/RestyleManager.h"
+
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/DocumentStyleRootIterator.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/GeckoBindings.h"
+#include "mozilla/LayerAnimationInfo.h"
+#include "mozilla/layers/AnimationInfo.h"
+#include "mozilla/layout/ScrollAnchorContainer.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGObserverUtils.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ViewportFrame.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/dom/ChildIterator.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLInputElement.h"
+
+#include "ScrollSnap.h"
+#include "nsAnimationManager.h"
+#include "nsBlockFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsContentUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSRendering.h"
+#include "nsDocShell.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsImageFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPrintfCString.h"
+#include "nsRefreshDriver.h"
+#include "nsStyleChangeList.h"
+#include "nsStyleUtil.h"
+#include "nsTransitionManager.h"
+#include "StickyScrollContainer.h"
+#include "ActiveLayerTracker.h"
+
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+using mozilla::layers::AnimationInfo;
+using mozilla::layout::ScrollAnchorContainer;
+
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+namespace mozilla {
+
+RestyleManager::RestyleManager(nsPresContext* aPresContext)
+ : mPresContext(aPresContext),
+ mRestyleGeneration(1),
+ mUndisplayedRestyleGeneration(1),
+ mInStyleRefresh(false),
+ mAnimationGeneration(0) {
+ MOZ_ASSERT(mPresContext);
+}
+
+void RestyleManager::ContentInserted(nsIContent* aChild) {
+ MOZ_ASSERT(aChild->GetParentNode());
+ if (aChild->IsElement()) {
+ StyleSet()->MaybeInvalidateForElementInsertion(*aChild->AsElement());
+ }
+ RestyleForInsertOrChange(aChild);
+}
+
+void RestyleManager::ContentAppended(nsIContent* aFirstNewContent) {
+ auto* container = aFirstNewContent->GetParentNode();
+ MOZ_ASSERT(container);
+
+#ifdef DEBUG
+ {
+ for (nsIContent* cur = aFirstNewContent; cur; cur = cur->GetNextSibling()) {
+ NS_ASSERTION(!cur->IsRootOfNativeAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+ }
+ }
+#endif
+ StyleSet()->MaybeInvalidateForElementAppend(*aFirstNewContent);
+
+ const auto selectorFlags = container->GetSelectorFlags() &
+ NodeSelectorFlags::AllSimpleRestyleFlagsForAppend;
+ if (!selectorFlags) {
+ return;
+ }
+
+ // The container cannot be a document.
+ MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
+
+ if (selectorFlags & NodeSelectorFlags::HasEmptySelector) {
+ // see whether we need to restyle the container
+ bool wasEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* cur = container->GetFirstChild(); cur != aFirstNewContent;
+ cur = cur->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(cur, false)) {
+ wasEmpty = false;
+ break;
+ }
+ }
+ if (wasEmpty && container->IsElement()) {
+ RestyleForEmptyChange(container->AsElement());
+ return;
+ }
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
+ if (container->IsElement()) {
+ auto* containerElement = container->AsElement();
+ PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ containerElement->GetFirstElementChild());
+ }
+ } else {
+ RestylePreviousSiblings(aFirstNewContent);
+ RestyleSiblingsStartingWith(aFirstNewContent);
+ }
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
+ // restyle the last element child before this node
+ for (nsIContent* cur = aFirstNewContent->GetPreviousSibling(); cur;
+ cur = cur->GetPreviousSibling()) {
+ if (cur->IsElement()) {
+ auto* element = cur->AsElement();
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ *element);
+ break;
+ }
+ }
+ }
+}
+
+void RestyleManager::RestylePreviousSiblings(nsIContent* aStartingSibling) {
+ for (nsIContent* sibling = aStartingSibling; sibling;
+ sibling = sibling->GetPreviousSibling()) {
+ if (auto* element = Element::FromNode(sibling)) {
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ }
+ }
+}
+
+void RestyleManager::RestyleSiblingsStartingWith(nsIContent* aStartingSibling) {
+ for (nsIContent* sibling = aStartingSibling; sibling;
+ sibling = sibling->GetNextSibling()) {
+ if (auto* element = Element::FromNode(sibling)) {
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ }
+ }
+}
+
+void RestyleManager::RestyleForEmptyChange(Element* aContainer) {
+ PostRestyleEvent(aContainer, RestyleHint::RestyleSubtree(), nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForEmptyDependency(*aContainer);
+
+ // In some cases (:empty + E, :empty ~ E), a change in the content of
+ // an element requires restyling its parent's siblings.
+ nsIContent* grandparent = aContainer->GetParent();
+ if (!grandparent || !(grandparent->GetSelectorFlags() &
+ NodeSelectorFlags::HasSlowSelectorLaterSiblings)) {
+ return;
+ }
+ RestyleSiblingsStartingWith(aContainer->GetNextSibling());
+}
+
+void RestyleManager::MaybeRestyleForEdgeChildChange(nsINode* aContainer,
+ nsIContent* aChangedChild) {
+ MOZ_ASSERT(aContainer->GetSelectorFlags() &
+ NodeSelectorFlags::HasEdgeChildSelector);
+ MOZ_ASSERT(aChangedChild->GetParent() == aContainer);
+ // restyle the previously-first element child if it is after this node
+ bool passedChild = false;
+ for (nsIContent* content = aContainer->GetFirstChild(); content;
+ content = content->GetNextSibling()) {
+ if (content == aChangedChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ auto* element = content->AsElement();
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ *element);
+ }
+ break;
+ }
+ }
+ // restyle the previously-last element child if it is before this node
+ passedChild = false;
+ for (nsIContent* content = aContainer->GetLastChild(); content;
+ content = content->GetPreviousSibling()) {
+ if (content == aChangedChild) {
+ passedChild = true;
+ continue;
+ }
+ if (content->IsElement()) {
+ if (passedChild) {
+ auto* element = content->AsElement();
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ *element);
+ }
+ break;
+ }
+ }
+}
+
+template <typename CharT>
+bool WhitespaceOnly(const CharT* aBuffer, size_t aUpTo) {
+ for (auto index : IntegerRange(aUpTo)) {
+ if (!dom::IsSpaceCharacter(aBuffer[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+template <typename CharT>
+bool WhitespaceOnlyChangedOnAppend(const CharT* aBuffer, size_t aOldLength,
+ size_t aNewLength) {
+ MOZ_ASSERT(aOldLength <= aNewLength);
+ if (!WhitespaceOnly(aBuffer, aOldLength)) {
+ // The old text was already not whitespace-only.
+ return false;
+ }
+
+ return !WhitespaceOnly(aBuffer + aOldLength, aNewLength - aOldLength);
+}
+
+static bool HasAnySignificantSibling(Element* aContainer, nsIContent* aChild) {
+ MOZ_ASSERT(aChild->GetParent() == aContainer);
+ for (nsIContent* child = aContainer->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child == aChild) {
+ continue;
+ }
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, false)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void RestyleManager::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
+ nsINode* parent = aContent->GetParentNode();
+ MOZ_ASSERT(parent, "How were we notified of a stray node?");
+
+ const auto slowSelectorFlags =
+ parent->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
+ if (!(slowSelectorFlags & (NodeSelectorFlags::HasEmptySelector |
+ NodeSelectorFlags::HasEdgeChildSelector))) {
+ // Nothing to do, no other slow selector can change as a result of this.
+ return;
+ }
+
+ if (!aContent->IsText()) {
+ // Doesn't matter to styling (could be a processing instruction or a
+ // comment), it can't change whether any selectors match or don't.
+ return;
+ }
+
+ if (MOZ_UNLIKELY(!parent->IsElement())) {
+ MOZ_ASSERT(parent->IsShadowRoot());
+ return;
+ }
+
+ if (MOZ_UNLIKELY(aContent->IsRootOfNativeAnonymousSubtree())) {
+ // This is an anonymous node and thus isn't in child lists, so isn't taken
+ // into account for selector matching the relevant selectors here.
+ return;
+ }
+
+ // Handle appends specially since they're common and we can know both the old
+ // and the new text exactly.
+ //
+ // TODO(emilio): This could be made much more general if :-moz-only-whitespace
+ // / :-moz-first-node and :-moz-last-node didn't exist. In that case we only
+ // need to know whether we went from empty to non-empty, and that's trivial to
+ // know, with CharacterDataChangeInfo...
+ if (!aInfo.mAppend) {
+ // FIXME(emilio): This restyles unnecessarily if the text node is the only
+ // child of the parent element. Fortunately, it's uncommon to have such
+ // nodes and this not being an append.
+ //
+ // See the testcase in bug 1427625 for a test-case that triggers this.
+ RestyleForInsertOrChange(aContent);
+ return;
+ }
+
+ const nsTextFragment* text = &aContent->AsText()->TextFragment();
+
+ const size_t oldLength = aInfo.mChangeStart;
+ const size_t newLength = text->GetLength();
+
+ const bool emptyChanged = !oldLength && newLength;
+
+ const bool whitespaceOnlyChanged =
+ text->Is2b()
+ ? WhitespaceOnlyChangedOnAppend(text->Get2b(), oldLength, newLength)
+ : WhitespaceOnlyChangedOnAppend(text->Get1b(), oldLength, newLength);
+
+ if (!emptyChanged && !whitespaceOnlyChanged) {
+ return;
+ }
+
+ if (slowSelectorFlags & NodeSelectorFlags::HasEmptySelector) {
+ if (!HasAnySignificantSibling(parent->AsElement(), aContent)) {
+ // We used to be empty, restyle the parent.
+ RestyleForEmptyChange(parent->AsElement());
+ return;
+ }
+ }
+
+ if (slowSelectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
+ MaybeRestyleForEdgeChildChange(parent, aContent);
+ }
+}
+
+// Restyling for a ContentInserted or CharacterDataChanged notification.
+// This could be used for ContentRemoved as well if we got the
+// notification before the removal happened (and sometimes
+// CharacterDataChanged is more like a removal than an addition).
+// The comments are written and variables are named in terms of it being
+// a ContentInserted notification.
+void RestyleManager::RestyleForInsertOrChange(nsIContent* aChild) {
+ nsINode* container = aChild->GetParentNode();
+ MOZ_ASSERT(container);
+
+ const auto selectorFlags =
+ container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
+ if (!selectorFlags) {
+ return;
+ }
+
+ NS_ASSERTION(!aChild->IsRootOfNativeAnonymousSubtree(),
+ "anonymous nodes should not be in child lists");
+
+ // The container cannot be a document.
+ MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
+
+ if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
+ container->IsElement()) {
+ // See whether we need to restyle the container due to :empty /
+ // :-moz-only-whitespace.
+ const bool wasEmpty =
+ !HasAnySignificantSibling(container->AsElement(), aChild);
+ if (wasEmpty) {
+ // FIXME(emilio): When coming from CharacterDataChanged this can restyle
+ // unnecessarily. Also can restyle unnecessarily if aChild is not
+ // significant anyway, though that's more unlikely.
+ RestyleForEmptyChange(container->AsElement());
+ return;
+ }
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
+ if (container->IsElement()) {
+ auto* containerElement = container->AsElement();
+ PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ containerElement->GetFirstElementChild());
+ }
+ } else {
+ RestylePreviousSiblings(aChild);
+ RestyleSiblingsStartingWith(aChild);
+ }
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(aChild->GetNextSibling());
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ aChild->GetNextElementSibling());
+ }
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
+ MaybeRestyleForEdgeChildChange(container, aChild);
+ }
+}
+
+void RestyleManager::ContentRemoved(nsIContent* aOldChild,
+ nsIContent* aFollowingSibling) {
+ auto* container = aOldChild->GetParentNode();
+ MOZ_ASSERT(container);
+
+ // Computed style data isn't useful for detached nodes, and we'll need to
+ // recompute it anyway if we ever insert the nodes back into a document.
+ if (auto* element = Element::FromNode(aOldChild)) {
+ RestyleManager::ClearServoDataFromSubtree(element);
+ // If this element is undisplayed or may have undisplayed descendants, we
+ // need to invalidate the cache, since there's the unlikely event of those
+ // elements getting destroyed and their addresses reused in a way that we
+ // look up the cache with their address for a different element before it's
+ // invalidated.
+ IncrementUndisplayedRestyleGeneration();
+ }
+ if (aOldChild->IsElement()) {
+ StyleSet()->MaybeInvalidateForElementRemove(*aOldChild->AsElement(),
+ aFollowingSibling);
+ }
+
+ const auto selectorFlags =
+ container->GetSelectorFlags() & NodeSelectorFlags::AllSimpleRestyleFlags;
+ if (!selectorFlags) {
+ return;
+ }
+
+ if (aOldChild->IsRootOfNativeAnonymousSubtree()) {
+ // This should be an assert, but this is called incorrectly in
+ // HTMLEditor::DeleteRefToAnonymousNode and the assertions were clogging
+ // up the logs. Make it an assert again when that's fixed.
+ MOZ_ASSERT(aOldChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+ "anonymous nodes should not be in child lists (bug 439258)");
+ }
+
+ // The container cannot be a document.
+ MOZ_ASSERT(container->IsElement() || container->IsShadowRoot());
+
+ if (selectorFlags & NodeSelectorFlags::HasEmptySelector &&
+ container->IsElement()) {
+ // see whether we need to restyle the container
+ bool isEmpty = true; // :empty or :-moz-only-whitespace
+ for (nsIContent* child = container->GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ // We don't know whether we're testing :empty or :-moz-only-whitespace,
+ // so be conservative and assume :-moz-only-whitespace (i.e., make
+ // IsSignificantChild less likely to be true, and thus make us more
+ // likely to restyle).
+ if (nsStyleUtil::IsSignificantChild(child, false)) {
+ isEmpty = false;
+ break;
+ }
+ }
+ if (isEmpty && container->IsElement()) {
+ RestyleForEmptyChange(container->AsElement());
+ return;
+ }
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelector) {
+ if (container->IsElement()) {
+ auto* containerElement = container->AsElement();
+ PostRestyleEvent(containerElement, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ containerElement->GetFirstElementChild());
+ }
+ } else {
+ RestylePreviousSiblings(aOldChild);
+ RestyleSiblingsStartingWith(aOldChild);
+ }
+ // Restyling the container is the most we can do here, so we're done.
+ return;
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorLaterSiblings) {
+ // Restyle all later siblings.
+ RestyleSiblingsStartingWith(aFollowingSibling);
+ if (selectorFlags & NodeSelectorFlags::HasSlowSelectorNthAll) {
+ Element* nextSibling =
+ aFollowingSibling ? aFollowingSibling->IsElement()
+ ? aFollowingSibling->AsElement()
+ : aFollowingSibling->GetNextElementSibling()
+ : nullptr;
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthDependencyFromSibling(
+ nextSibling);
+ }
+ }
+
+ if (selectorFlags & NodeSelectorFlags::HasEdgeChildSelector) {
+ // restyle the now-first element child if it was after aOldChild
+ bool reachedFollowingSibling = false;
+ for (nsIContent* content = container->GetFirstChild(); content;
+ content = content->GetNextSibling()) {
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ // do NOT continue here; we might want to restyle this node
+ }
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ auto* element = content->AsElement();
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ *element);
+ }
+ break;
+ }
+ }
+ // restyle the now-last element child if it was before aOldChild
+ reachedFollowingSibling = (aFollowingSibling == nullptr);
+ for (nsIContent* content = container->GetLastChild(); content;
+ content = content->GetPreviousSibling()) {
+ if (content->IsElement()) {
+ if (reachedFollowingSibling) {
+ auto* element = content->AsElement();
+ PostRestyleEvent(element, RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ StyleSet()->MaybeInvalidateRelativeSelectorForNthEdgeDependency(
+ *element);
+ }
+ break;
+ }
+ if (content == aFollowingSibling) {
+ reachedFollowingSibling = true;
+ }
+ }
+ }
+}
+
+static bool StateChangeMayAffectFrame(const Element& aElement,
+ const nsIFrame& aFrame,
+ ElementState aStates) {
+ const bool brokenChanged = aStates.HasState(ElementState::BROKEN);
+ if (!brokenChanged) {
+ return false;
+ }
+
+ if (aFrame.IsGeneratedContentFrame()) {
+ // If it's other generated content, ignore state changes on it.
+ return aElement.IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage);
+ }
+
+ if (aElement.IsAnyOfHTMLElements(nsGkAtoms::object, nsGkAtoms::embed)) {
+ // Broken affects object fallback behavior.
+ return true;
+ }
+
+ const bool mightChange = [&] {
+ if (aElement.IsHTMLElement(nsGkAtoms::img)) {
+ return true;
+ }
+ const auto* input = HTMLInputElement::FromNode(aElement);
+ return input && input->ControlType() == FormControlType::InputImage;
+ }();
+
+ if (!mightChange) {
+ return false;
+ }
+
+ const bool needsImageFrame =
+ nsImageFrame::ImageFrameTypeFor(aElement, *aFrame.Style()) !=
+ nsImageFrame::ImageFrameType::None;
+ return needsImageFrame != aFrame.IsImageFrameOrSubclass();
+}
+
+static bool RepaintForAppearance(nsIFrame& aFrame, const Element& aElement,
+ ElementState aStateMask) {
+ if (aStateMask.HasAtLeastOneOfStates(ElementState::HOVER |
+ ElementState::ACTIVE) &&
+ aElement.IsAnyOfXULElements(nsGkAtoms::checkbox, nsGkAtoms::radio)) {
+ // The checkbox inside these elements inherit hover state and so on, see
+ // nsNativeTheme::GetContentState.
+ // FIXME(emilio): Would be nice to not have these hard-coded.
+ return true;
+ }
+ auto appearance = aFrame.StyleDisplay()->EffectiveAppearance();
+ if (appearance == StyleAppearance::None) {
+ return false;
+ }
+ nsPresContext* pc = aFrame.PresContext();
+ nsITheme* theme = pc->Theme();
+ if (!theme->ThemeSupportsWidget(pc, &aFrame, appearance)) {
+ return false;
+ }
+ bool repaint = false;
+ theme->WidgetStateChanged(&aFrame, appearance, nullptr, &repaint, nullptr);
+ return repaint;
+}
+
+/**
+ * Calculates the change hint and the restyle hint for a given content state
+ * change.
+ */
+static nsChangeHint ChangeForContentStateChange(const Element& aElement,
+ ElementState aStateMask) {
+ auto changeHint = nsChangeHint(0);
+
+ // Any change to a content state that affects which frames we construct
+ // must lead to a frame reconstruct here if we already have a frame.
+ // Note that we never decide through non-CSS means to not create frames
+ // based on content states, so if we already don't have a frame we don't
+ // need to force a reframe -- if it's needed, the HasStateDependentStyle
+ // call will handle things.
+ if (nsIFrame* primaryFrame = aElement.GetPrimaryFrame()) {
+ if (StateChangeMayAffectFrame(aElement, *primaryFrame, aStateMask)) {
+ return nsChangeHint_ReconstructFrame;
+ }
+ if (RepaintForAppearance(*primaryFrame, aElement, aStateMask)) {
+ changeHint |= nsChangeHint_RepaintFrame;
+ }
+ primaryFrame->ElementStateChanged(aStateMask);
+ }
+
+ if (aStateMask.HasState(ElementState::VISITED)) {
+ // Exposing information to the page about whether the link is
+ // visited or not isn't really something we can worry about here.
+ // FIXME: We could probably do this a bit better.
+ changeHint |= nsChangeHint_RepaintFrame;
+ }
+
+ // This changes the applicable text-transform in the editor root.
+ if (aStateMask.HasState(ElementState::REVEALED)) {
+ // This is the same change hint as tweaking text-transform.
+ changeHint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ return changeHint;
+}
+
+#ifdef DEBUG
+/* static */
+nsCString RestyleManager::ChangeHintToString(nsChangeHint aHint) {
+ nsCString result;
+ bool any = false;
+ const char* names[] = {"RepaintFrame",
+ "NeedReflow",
+ "ClearAncestorIntrinsics",
+ "ClearDescendantIntrinsics",
+ "NeedDirtyReflow",
+ "UpdateCursor",
+ "UpdateEffects",
+ "UpdateOpacityLayer",
+ "UpdateTransformLayer",
+ "ReconstructFrame",
+ "UpdateOverflow",
+ "UpdateSubtreeOverflow",
+ "UpdatePostTransformOverflow",
+ "UpdateParentOverflow",
+ "ChildrenOnlyTransform",
+ "RecomputePosition",
+ "UpdateContainingBlock",
+ "BorderStyleNoneChange",
+ "SchedulePaint",
+ "NeutralChange",
+ "InvalidateRenderingObservers",
+ "ReflowChangesSizeOrPosition",
+ "UpdateComputedBSize",
+ "UpdateUsesOpacity",
+ "UpdateBackgroundPosition",
+ "AddOrRemoveTransform",
+ "ScrollbarChange",
+ "UpdateTableCellSpans",
+ "VisibilityChange"};
+ static_assert(nsChangeHint_AllHints ==
+ static_cast<uint32_t>((1ull << ArrayLength(names)) - 1),
+ "Name list doesn't match change hints.");
+ uint32_t hint =
+ aHint & static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
+ uint32_t rest =
+ aHint & ~static_cast<uint32_t>((1ull << ArrayLength(names)) - 1);
+ if ((hint & NS_STYLE_HINT_REFLOW) == NS_STYLE_HINT_REFLOW) {
+ result.AppendLiteral("NS_STYLE_HINT_REFLOW");
+ hint = hint & ~NS_STYLE_HINT_REFLOW;
+ any = true;
+ } else if ((hint & nsChangeHint_AllReflowHints) ==
+ nsChangeHint_AllReflowHints) {
+ result.AppendLiteral("nsChangeHint_AllReflowHints");
+ hint = hint & ~nsChangeHint_AllReflowHints;
+ any = true;
+ } else if ((hint & NS_STYLE_HINT_VISUAL) == NS_STYLE_HINT_VISUAL) {
+ result.AppendLiteral("NS_STYLE_HINT_VISUAL");
+ hint = hint & ~NS_STYLE_HINT_VISUAL;
+ any = true;
+ }
+ for (uint32_t i = 0; i < ArrayLength(names); i++) {
+ if (hint & (1u << i)) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("nsChangeHint_%s", names[i]);
+ any = true;
+ }
+ }
+ if (rest) {
+ if (any) {
+ result.AppendLiteral(" | ");
+ }
+ result.AppendPrintf("0x%0x", rest);
+ } else {
+ if (!any) {
+ result.AppendLiteral("nsChangeHint(0)");
+ }
+ }
+ return result;
+}
+#endif
+
+/**
+ * Frame construction helpers follow.
+ */
+#ifdef DEBUG
+static bool gInApplyRenderingChangeToTree = false;
+#endif
+
+/**
+ * Sync views on the frame and all of it's descendants (following placeholders).
+ * The change hint should be some combination of nsChangeHint_RepaintFrame,
+ * nsChangeHint_UpdateOpacityLayer and nsChangeHint_SchedulePaint, nothing else.
+ */
+static void SyncViewsAndInvalidateDescendants(nsIFrame*, nsChangeHint);
+
+static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint);
+
+/**
+ * This helper function is used to find the correct SVG frame to target when we
+ * encounter nsChangeHint_ChildrenOnlyTransform; needed since sometimes we end
+ * up handling that hint while processing hints for one of the SVG frame's
+ * ancestor frames.
+ *
+ * The reason that we sometimes end up trying to process the hint for an
+ * ancestor of the SVG frame that the hint is intended for is due to the way we
+ * process restyle events. ApplyRenderingChangeToTree adjusts the frame from
+ * the restyled element's principle frame to one of its ancestor frames based
+ * on what nsCSSRendering::FindBackground returns, since the background style
+ * may have been propagated up to an ancestor frame. Processing hints using an
+ * ancestor frame is fine in general, but nsChangeHint_ChildrenOnlyTransform is
+ * a special case since it is intended to update a specific frame.
+ */
+static nsIFrame* GetFrameForChildrenOnlyTransformHint(nsIFrame* aFrame) {
+ if (aFrame->IsViewportFrame()) {
+ // This happens if the root-<svg> is fixed positioned, in which case we
+ // can't use aFrame->GetContent() to find the primary frame, since
+ // GetContent() returns nullptr for ViewportFrame.
+ aFrame = aFrame->PrincipalChildList().FirstChild();
+ }
+ // For an nsHTMLScrollFrame, this will get the SVG frame that has the
+ // children-only transforms:
+ aFrame = aFrame->GetContent()->GetPrimaryFrame();
+ if (aFrame->IsSVGOuterSVGFrame()) {
+ aFrame = aFrame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(aFrame->IsSVGOuterSVGAnonChildFrame(),
+ "Where is the SVGOuterSVGFrame's anon child??");
+ }
+ MOZ_ASSERT(aFrame->IsSVGContainerFrame(),
+ "Children-only transforms only expected on SVG frames");
+ return aFrame;
+}
+
+// This function tries to optimize a position style change by either
+// moving aFrame or ignoring the style change when it's safe to do so.
+// It returns true when that succeeds, otherwise it posts a reflow request
+// and returns false.
+static bool RecomputePosition(nsIFrame* aFrame) {
+ // It's pointless to move around frames that have never been reflowed or
+ // are dirty (i.e. they will be reflowed), or aren't affected by position
+ // styles.
+ if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+ NS_FRAME_SVG_LAYOUT)) {
+ return true;
+ }
+
+ // Don't process position changes on table frames, since we already handle
+ // the dynamic position change on the table wrapper frame, and the
+ // reflow-based fallback code path also ignores positions on inner table
+ // frames.
+ if (aFrame->IsTableFrame()) {
+ return true;
+ }
+
+ const nsStyleDisplay* display = aFrame->StyleDisplay();
+ // Changes to the offsets of a non-positioned element can safely be ignored.
+ if (display->mPosition == StylePositionProperty::Static) {
+ return true;
+ }
+
+ // Don't process position changes on frames which have views or the ones which
+ // have a view somewhere in their descendants, because the corresponding view
+ // needs to be repositioned properly as well.
+ if (aFrame->HasView() ||
+ aFrame->HasAnyStateBits(NS_FRAME_HAS_CHILD_WITH_VIEW)) {
+ return false;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // If the frame has an intrinsic block-size, we resolve its 'auto' margins
+ // after doing layout, since we need to know the frame's block size. See
+ // nsAbsoluteContainingBlock::ResolveAutoMarginsAfterLayout().
+ //
+ // Since the size of the frame doesn't change, we could modify the below
+ // computation to compute the margin correctly without doing a full reflow,
+ // however we decided to try doing a full reflow for now.
+ if (aFrame->HasIntrinsicKeywordForBSize()) {
+ WritingMode wm = aFrame->GetWritingMode();
+ const auto* styleMargin = aFrame->StyleMargin();
+ if (styleMargin->HasBlockAxisAuto(wm)) {
+ return false;
+ }
+ }
+ // Flexbox and Grid layout supports CSS Align and the optimizations below
+ // don't support that yet.
+ nsIFrame* ph = aFrame->GetPlaceholderFrame();
+ if (ph && ph->HasAnyStateBits(PLACEHOLDER_STATICPOS_NEEDS_CSSALIGN)) {
+ return false;
+ }
+ }
+
+ // If we need to reposition any descendant that depends on our static
+ // position, then we also can't take the optimized path.
+ //
+ // TODO(emilio): It may be worth trying to find them and try to call
+ // RecomputePosition on them too instead of disabling the optimization...
+ if (aFrame->DescendantMayDependOnItsStaticPosition()) {
+ return false;
+ }
+
+ aFrame->SchedulePaint();
+
+ auto postPendingScrollAnchorOrResnap = [](nsIFrame* frame) {
+ if (frame->IsInScrollAnchorChain()) {
+ ScrollAnchorContainer* container = ScrollAnchorContainer::FindFor(frame);
+ frame->PresShell()->PostPendingScrollAnchorAdjustment(container);
+ }
+
+ // We need to trigger re-snapping to this content if we snapped to the
+ // content on the last scroll operation.
+ ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
+ };
+
+ // For relative positioning, we can simply update the frame rect
+ if (display->IsRelativelyOrStickyPositionedStyle()) {
+ if (aFrame->IsGridItem()) {
+ // A grid item's CB is its grid area, not the parent frame content area
+ // as is assumed below.
+ return false;
+ }
+ // Move the frame
+ if (display->mPosition == StylePositionProperty::Sticky) {
+ // Update sticky positioning for an entire element at once, starting with
+ // the first continuation or ib-split sibling.
+ // It's rare that the frame we already have isn't already the first
+ // continuation or ib-split sibling, but it can happen when styles differ
+ // across continuations such as ::first-line or ::first-letter, and in
+ // those cases we will generally (but maybe not always) do the work twice.
+ nsIFrame* firstContinuation =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+
+ StickyScrollContainer::ComputeStickyOffsets(firstContinuation);
+ StickyScrollContainer* ssc =
+ StickyScrollContainer::GetStickyScrollContainerForFrame(
+ firstContinuation);
+ if (ssc) {
+ ssc->PositionContinuations(firstContinuation);
+ }
+ } else {
+ MOZ_ASSERT(display->IsRelativelyPositionedStyle(),
+ "Unexpected type of positioning");
+ for (nsIFrame* cont = aFrame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ nsIFrame* cb = cont->GetContainingBlock();
+ WritingMode wm = cb->GetWritingMode();
+ const LogicalSize cbSize = cb->ContentSize();
+ const LogicalMargin newLogicalOffsets =
+ ReflowInput::ComputeRelativeOffsets(wm, cont, cbSize);
+ const nsMargin newOffsets = newLogicalOffsets.GetPhysicalMargin(wm);
+
+ // ReflowInput::ApplyRelativePositioning would work here, but
+ // since we've already checked mPosition and aren't changing the frame's
+ // normal position, go ahead and add the offsets directly.
+ // First, we need to ensure that the normal position is stored though.
+ bool hasProperty;
+ nsPoint normalPosition = cont->GetNormalPosition(&hasProperty);
+ if (!hasProperty) {
+ cont->AddProperty(nsIFrame::NormalPositionProperty(), normalPosition);
+ }
+ cont->SetPosition(normalPosition +
+ nsPoint(newOffsets.left, newOffsets.top));
+ }
+ }
+
+ postPendingScrollAnchorOrResnap(aFrame);
+ return true;
+ }
+
+ // For the absolute positioning case, set up a fake HTML reflow input for
+ // the frame, and then get the offsets and size from it. If the frame's size
+ // doesn't need to change, we can simply update the frame position. Otherwise
+ // we fall back to a reflow.
+ UniquePtr<gfxContext> rc =
+ aFrame->PresShell()->CreateReferenceRenderingContext();
+
+ // Construct a bogus parent reflow input so that there's a usable reflow input
+ // for the containing block.
+ nsIFrame* parentFrame = aFrame->GetParent();
+ WritingMode parentWM = parentFrame->GetWritingMode();
+ WritingMode frameWM = aFrame->GetWritingMode();
+ LogicalSize parentSize = parentFrame->GetLogicalSize();
+
+ nsFrameState savedState = parentFrame->GetStateBits();
+ ReflowInput parentReflowInput(aFrame->PresContext(), parentFrame, rc.get(),
+ parentSize);
+ parentFrame->RemoveStateBits(~nsFrameState(0));
+ parentFrame->AddStateBits(savedState);
+
+ // The bogus parent state here was created with no parent state of its own,
+ // and therefore it won't have an mCBReflowInput set up.
+ // But we may need one (for InitCBReflowInput in a child state), so let's
+ // try to create one here for the cases where it will be needed.
+ Maybe<ReflowInput> cbReflowInput;
+ nsIFrame* cbFrame = parentFrame->GetContainingBlock();
+ if (cbFrame && (aFrame->GetContainingBlock() != parentFrame ||
+ parentFrame->IsTableFrame())) {
+ const auto cbWM = cbFrame->GetWritingMode();
+ LogicalSize cbSize = cbFrame->GetLogicalSize();
+ cbReflowInput.emplace(cbFrame->PresContext(), cbFrame, rc.get(), cbSize);
+ cbReflowInput->SetComputedLogicalMargin(
+ cbWM, cbFrame->GetLogicalUsedMargin(cbWM));
+ cbReflowInput->SetComputedLogicalPadding(
+ cbWM, cbFrame->GetLogicalUsedPadding(cbWM));
+ cbReflowInput->SetComputedLogicalBorderPadding(
+ cbWM, cbFrame->GetLogicalUsedBorderAndPadding(cbWM));
+ parentReflowInput.mCBReflowInput = cbReflowInput.ptr();
+ }
+
+ NS_WARNING_ASSERTION(parentSize.ISize(parentWM) != NS_UNCONSTRAINEDSIZE &&
+ parentSize.BSize(parentWM) != NS_UNCONSTRAINEDSIZE,
+ "parentSize should be valid");
+ parentReflowInput.SetComputedISize(std::max(parentSize.ISize(parentWM), 0));
+ parentReflowInput.SetComputedBSize(std::max(parentSize.BSize(parentWM), 0));
+ parentReflowInput.SetComputedLogicalMargin(parentWM, LogicalMargin(parentWM));
+
+ parentReflowInput.SetComputedLogicalPadding(
+ parentWM, parentFrame->GetLogicalUsedPadding(parentWM));
+ parentReflowInput.SetComputedLogicalBorderPadding(
+ parentWM, parentFrame->GetLogicalUsedBorderAndPadding(parentWM));
+ LogicalSize availSize = parentSize.ConvertTo(frameWM, parentWM);
+ availSize.BSize(frameWM) = NS_UNCONSTRAINEDSIZE;
+
+ ViewportFrame* viewport = do_QueryFrame(parentFrame);
+ nsSize cbSize =
+ viewport
+ ? viewport->AdjustReflowInputAsContainingBlock(&parentReflowInput)
+ .Size()
+ : aFrame->GetContainingBlock()->GetSize();
+ const nsMargin& parentBorder =
+ parentReflowInput.mStyleBorder->GetComputedBorder();
+ cbSize -= nsSize(parentBorder.LeftRight(), parentBorder.TopBottom());
+ LogicalSize lcbSize(frameWM, cbSize);
+ ReflowInput reflowInput(aFrame->PresContext(), parentReflowInput, aFrame,
+ availSize, Some(lcbSize));
+ nscoord computedISize = reflowInput.ComputedISize();
+ nscoord computedBSize = reflowInput.ComputedBSize();
+ const auto frameBP = reflowInput.ComputedLogicalBorderPadding(frameWM);
+ computedISize += frameBP.IStartEnd(frameWM);
+ if (computedBSize != NS_UNCONSTRAINEDSIZE) {
+ computedBSize += frameBP.BStartEnd(frameWM);
+ }
+ LogicalSize logicalSize = aFrame->GetLogicalSize(frameWM);
+ nsSize size = aFrame->GetSize();
+ // The RecomputePosition hint is not used if any offset changed between auto
+ // and non-auto. If computedSize.height == NS_UNCONSTRAINEDSIZE then the new
+ // element height will be its intrinsic height, and since 'top' and 'bottom''s
+ // auto-ness hasn't changed, the old height must also be its intrinsic
+ // height, which we can assume hasn't changed (or reflow would have
+ // been triggered).
+ if (computedISize == logicalSize.ISize(frameWM) &&
+ (computedBSize == NS_UNCONSTRAINEDSIZE ||
+ computedBSize == logicalSize.BSize(frameWM))) {
+ // If we're solving for 'left' or 'top', then compute it here, in order to
+ // match the reflow code path.
+ //
+ // TODO(emilio): It'd be nice if this did logical math instead, but it seems
+ // to me the math should work out on vertical writing modes as well. See Bug
+ // 1675861 for some hints.
+ const nsMargin offset = reflowInput.ComputedPhysicalOffsets();
+ const nsMargin margin = reflowInput.ComputedPhysicalMargin();
+
+ nscoord left = offset.left;
+ if (left == NS_AUTOOFFSET) {
+ left =
+ cbSize.width - offset.right - margin.right - size.width - margin.left;
+ }
+
+ nscoord top = offset.top;
+ if (top == NS_AUTOOFFSET) {
+ top = cbSize.height - offset.bottom - margin.bottom - size.height -
+ margin.top;
+ }
+
+ // Move the frame
+ nsPoint pos(parentBorder.left + left + margin.left,
+ parentBorder.top + top + margin.top);
+ aFrame->SetPosition(pos);
+
+ postPendingScrollAnchorOrResnap(aFrame);
+ return true;
+ }
+
+ // Fall back to a reflow
+ return false;
+}
+
+/**
+ * Return true if aFrame's subtree has placeholders for out-of-flow content
+ * that would be affected due to the change to
+ * `aPossiblyChangingContainingBlock` (and thus would need to get reframed).
+ *
+ * In particular, this function returns true if there are placeholders whose OOF
+ * frames may need to be reparented (via reframing) as a result of whatever
+ * change actually happened.
+ *
+ * The `aIs{Abs,Fixed}PosContainingBlock` params represent whether
+ * `aPossiblyChangingContainingBlock` is a containing block for abs pos / fixed
+ * pos stuff, respectively, for the _new_ style that the frame already has, not
+ * the old one.
+ */
+static bool ContainingBlockChangeAffectsDescendants(
+ nsIFrame* aPossiblyChangingContainingBlock, nsIFrame* aFrame,
+ bool aIsAbsPosContainingBlock, bool aIsFixedPosContainingBlock) {
+ // All fixed-pos containing blocks should also be abs-pos containing blocks.
+ MOZ_ASSERT_IF(aIsFixedPosContainingBlock, aIsAbsPosContainingBlock);
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* f : childList.mList) {
+ if (f->IsPlaceholderFrame()) {
+ nsIFrame* outOfFlow = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ // If SVG text frames could appear here, they could confuse us since
+ // they ignore their position style ... but they can't.
+ NS_ASSERTION(!outOfFlow->IsInSVGTextSubtree(),
+ "SVG text frames can't be out of flow");
+ // Top-layer frames don't change containing block based on direct
+ // ancestors.
+ auto* display = outOfFlow->StyleDisplay();
+ if (display->IsAbsolutelyPositionedStyle() &&
+ display->mTopLayer == StyleTopLayer::None) {
+ const bool isContainingBlock =
+ aIsFixedPosContainingBlock ||
+ (aIsAbsPosContainingBlock &&
+ display->mPosition == StylePositionProperty::Absolute);
+ // NOTE(emilio): aPossiblyChangingContainingBlock is guaranteed to be
+ // a first continuation, see the assertion in the caller.
+ nsIFrame* parent = outOfFlow->GetParent()->FirstContinuation();
+ if (isContainingBlock) {
+ // If we are becoming a containing block, we only need to reframe if
+ // this oof's current containing block is an ancestor of the new
+ // frame.
+ if (parent != aPossiblyChangingContainingBlock &&
+ nsLayoutUtils::IsProperAncestorFrame(
+ parent, aPossiblyChangingContainingBlock)) {
+ return true;
+ }
+ } else {
+ // If we are not a containing block anymore, we only need to reframe
+ // if we are the current containing block of the oof frame.
+ if (parent == aPossiblyChangingContainingBlock) {
+ return true;
+ }
+ }
+ }
+ }
+ // NOTE: It's tempting to check f->IsAbsPosContainingBlock() or
+ // f->IsFixedPosContainingBlock() here. However, that would only
+ // be testing the *new* style of the frame, which might exclude
+ // descendants that currently have this frame as an abs-pos
+ // containing block. Taking the codepath where we don't reframe
+ // could lead to an unsafe call to
+ // cont->MarkAsNotAbsoluteContainingBlock() before we've reframed
+ // the descendant and taken it off the absolute list.
+ if (ContainingBlockChangeAffectsDescendants(
+ aPossiblyChangingContainingBlock, f, aIsAbsPosContainingBlock,
+ aIsFixedPosContainingBlock)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+// Returns the frame that would serve as the containing block for aFrame's
+// positioned descendants, if aFrame had styles to make it a CB for such
+// descendants. (Typically this is just aFrame itself, or its insertion frame).
+//
+// Returns nullptr if this frame can't be easily determined.
+static nsIFrame* ContainingBlockForFrame(nsIFrame* aFrame) {
+ if (aFrame->IsFieldSetFrame()) {
+ // FIXME: This should be easily implementable.
+ return nullptr;
+ }
+ nsIFrame* insertionFrame = aFrame->GetContentInsertionFrame();
+ if (insertionFrame == aFrame) {
+ return insertionFrame;
+ }
+ // Generally frames with a different insertion frame are hard to deal with,
+ // but scrollframes are easy because the containing block is just the
+ // insertion frame.
+ if (aFrame->IsScrollFrame()) {
+ return insertionFrame;
+ }
+ // Combobox frames are easy as well because they can't have positioned
+ // children anyways.
+ // Button and table cell frames are also easy because the containing block is
+ // the frame itself.
+ if (aFrame->IsComboboxControlFrame() || aFrame->IsHTMLButtonControlFrame() ||
+ aFrame->IsTableCellFrame()) {
+ return aFrame;
+ }
+ return nullptr;
+}
+
+static bool NeedToReframeToUpdateContainingBlock(nsIFrame* aFrame,
+ nsIFrame* aMaybeChangingCB) {
+ // NOTE: This looks at the new style.
+ const bool isFixedContainingBlock = aFrame->IsFixedPosContainingBlock();
+ MOZ_ASSERT_IF(isFixedContainingBlock, aFrame->IsAbsPosContainingBlock());
+
+ const bool isAbsPosContainingBlock =
+ isFixedContainingBlock || aFrame->IsAbsPosContainingBlock();
+
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
+ if (ContainingBlockChangeAffectsDescendants(aMaybeChangingCB, f,
+ isAbsPosContainingBlock,
+ isFixedContainingBlock)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void DoApplyRenderingChangeToTree(nsIFrame* aFrame,
+ nsChangeHint aChange) {
+ MOZ_ASSERT(gInApplyRenderingChangeToTree,
+ "should only be called within ApplyRenderingChangeToTree");
+
+ for (; aFrame;
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) {
+ // Invalidate and sync views on all descendant frames, following
+ // placeholders. We don't need to update transforms in
+ // SyncViewsAndInvalidateDescendants, because there can't be any
+ // out-of-flows or popups that need to be transformed; all out-of-flow
+ // descendants of the transformed element must also be descendants of the
+ // transformed frame.
+ SyncViewsAndInvalidateDescendants(
+ aFrame, nsChangeHint(aChange & (nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_SchedulePaint)));
+ // This must be set to true if the rendering change needs to
+ // invalidate content. If it's false, a composite-only paint
+ // (empty transaction) will be scheduled.
+ bool needInvalidatingPaint = false;
+
+ // if frame has view, will already be invalidated
+ if (aChange & nsChangeHint_RepaintFrame) {
+ // Note that this whole block will be skipped when painting is suppressed
+ // (due to our caller ApplyRendingChangeToTree() discarding the
+ // nsChangeHint_RepaintFrame hint). If you add handling for any other
+ // hints within this block, be sure that they too should be ignored when
+ // painting is suppressed.
+ needInvalidatingPaint = true;
+ aFrame->InvalidateFrameSubtree();
+ if ((aChange & nsChangeHint_UpdateEffects) &&
+ aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // Need to update our overflow rects:
+ SVGUtils::ScheduleReflowSVG(aFrame);
+ }
+
+ ActiveLayerTracker::NotifyNeedsRepaint(aFrame);
+ }
+ if (aChange & nsChangeHint_UpdateOpacityLayer) {
+ // FIXME/bug 796697: we can get away with empty transactions for
+ // opacity updates in many cases.
+ needInvalidatingPaint = true;
+
+ ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_opacity);
+ if (SVGIntegrationUtils::UsingEffectsForFrame(aFrame)) {
+ // SVG effects paints the opacity without using
+ // nsDisplayOpacity. We need to invalidate manually.
+ aFrame->InvalidateFrameSubtree();
+ }
+ }
+ if ((aChange & nsChangeHint_UpdateTransformLayer) &&
+ aFrame->IsTransformed()) {
+ // Note: All the transform-like properties should map to the same
+ // layer activity index, so does the restyle count. Therefore, using
+ // eCSSProperty_transform should be fine.
+ ActiveLayerTracker::NotifyRestyle(aFrame, eCSSProperty_transform);
+ needInvalidatingPaint = true;
+ }
+ if (aChange & nsChangeHint_ChildrenOnlyTransform) {
+ needInvalidatingPaint = true;
+ nsIFrame* childFrame = GetFrameForChildrenOnlyTransformHint(aFrame)
+ ->PrincipalChildList()
+ .FirstChild();
+ for (; childFrame; childFrame = childFrame->GetNextSibling()) {
+ // Note: All the transform-like properties should map to the same
+ // layer activity index, so does the restyle count. Therefore, using
+ // eCSSProperty_transform should be fine.
+ ActiveLayerTracker::NotifyRestyle(childFrame, eCSSProperty_transform);
+ }
+ }
+ if (aChange & nsChangeHint_SchedulePaint) {
+ needInvalidatingPaint = true;
+ }
+ aFrame->SchedulePaint(needInvalidatingPaint
+ ? nsIFrame::PAINT_DEFAULT
+ : nsIFrame::PAINT_COMPOSITE_ONLY);
+ }
+}
+
+static void SyncViewsAndInvalidateDescendants(nsIFrame* aFrame,
+ nsChangeHint aChange) {
+ MOZ_ASSERT(gInApplyRenderingChangeToTree,
+ "should only be called within ApplyRenderingChangeToTree");
+
+ NS_ASSERTION(nsChangeHint_size_t(aChange) ==
+ (aChange & (nsChangeHint_RepaintFrame |
+ nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_SchedulePaint)),
+ "Invalid change flag");
+
+ aFrame->SyncFrameViewProperties();
+
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ for (nsIFrame* child : list) {
+ if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // only do frames that don't have placeholders
+ if (child->IsPlaceholderFrame()) {
+ // do the out-of-flow frame and its continuations
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(child);
+ DoApplyRenderingChangeToTree(outOfFlowFrame, aChange);
+ } else if (listID == FrameChildListID::Popup) {
+ DoApplyRenderingChangeToTree(child, aChange);
+ } else { // regular frame
+ SyncViewsAndInvalidateDescendants(child, aChange);
+ }
+ }
+ }
+ }
+}
+
+static void ApplyRenderingChangeToTree(PresShell* aPresShell, nsIFrame* aFrame,
+ nsChangeHint aChange) {
+ // We check StyleDisplay()->HasTransformStyle() in addition to checking
+ // IsTransformed() since we can get here for some frames that don't support
+ // CSS transforms, and table frames, which are their own odd-ball, since the
+ // transform is handled by their wrapper, which _also_ gets a separate hint.
+ NS_ASSERTION(!(aChange & nsChangeHint_UpdateTransformLayer) ||
+ aFrame->IsTransformed() ||
+ aFrame->StyleDisplay()->HasTransformStyle(),
+ "Unexpected UpdateTransformLayer hint");
+
+ if (aPresShell->IsPaintingSuppressed()) {
+ // Don't allow synchronous rendering changes when painting is turned off.
+ aChange &= ~nsChangeHint_RepaintFrame;
+ if (!aChange) {
+ return;
+ }
+ }
+
+// Trigger rendering updates by damaging this frame and any
+// continuations of this frame.
+#ifdef DEBUG
+ gInApplyRenderingChangeToTree = true;
+#endif
+ if (aChange & nsChangeHint_RepaintFrame) {
+ // If the frame is the primary frame of either the body element or
+ // the html element, we propagate the repaint change hint to the
+ // viewport. This is necessary for background and scrollbar colors
+ // propagation.
+ if (aFrame->IsPrimaryFrameOfRootOrBodyElement()) {
+ nsIFrame* rootFrame = aPresShell->GetRootFrame();
+ MOZ_ASSERT(rootFrame, "No root frame?");
+ DoApplyRenderingChangeToTree(rootFrame, nsChangeHint_RepaintFrame);
+ aChange &= ~nsChangeHint_RepaintFrame;
+ if (!aChange) {
+ return;
+ }
+ }
+ }
+ DoApplyRenderingChangeToTree(aFrame, aChange);
+#ifdef DEBUG
+ gInApplyRenderingChangeToTree = false;
+#endif
+}
+
+static void AddSubtreeToOverflowTracker(
+ nsIFrame* aFrame, OverflowChangedTracker& aOverflowChangedTracker) {
+ if (aFrame->FrameMaintainsOverflow()) {
+ aOverflowChangedTracker.AddFrame(aFrame,
+ OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ AddSubtreeToOverflowTracker(child, aOverflowChangedTracker);
+ }
+ }
+}
+
+static void StyleChangeReflow(nsIFrame* aFrame, nsChangeHint aHint) {
+ IntrinsicDirty dirtyType;
+ if (aHint & nsChangeHint_ClearDescendantIntrinsics) {
+ NS_ASSERTION(aHint & nsChangeHint_ClearAncestorIntrinsics,
+ "Please read the comments in nsChangeHint.h");
+ NS_ASSERTION(aHint & nsChangeHint_NeedDirtyReflow,
+ "ClearDescendantIntrinsics requires NeedDirtyReflow");
+ dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
+ } else if ((aHint & nsChangeHint_UpdateComputedBSize) &&
+ aFrame->HasAnyStateBits(
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ dirtyType = IntrinsicDirty::FrameAncestorsAndDescendants;
+ } else if (aHint & nsChangeHint_ClearAncestorIntrinsics) {
+ dirtyType = IntrinsicDirty::FrameAndAncestors;
+ } else {
+ dirtyType = IntrinsicDirty::None;
+ }
+
+ if (aHint & nsChangeHint_UpdateComputedBSize) {
+ aFrame->SetHasBSizeChange(true);
+ }
+
+ nsFrameState dirtyBits;
+ if (aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ dirtyBits = nsFrameState(0);
+ } else if ((aHint & nsChangeHint_NeedDirtyReflow) ||
+ dirtyType == IntrinsicDirty::FrameAncestorsAndDescendants) {
+ dirtyBits = NS_FRAME_IS_DIRTY;
+ } else {
+ dirtyBits = NS_FRAME_HAS_DIRTY_CHILDREN;
+ }
+
+ // If we're not going to clear any intrinsic sizes on the frames, and
+ // there are no dirty bits to set, then there's nothing to do.
+ if (dirtyType == IntrinsicDirty::None && !dirtyBits) return;
+
+ ReflowRootHandling rootHandling;
+ if (aHint & nsChangeHint_ReflowChangesSizeOrPosition) {
+ rootHandling = ReflowRootHandling::PositionOrSizeChange;
+ } else {
+ rootHandling = ReflowRootHandling::NoPositionOrSizeChange;
+ }
+
+ do {
+ aFrame->PresShell()->FrameNeedsReflow(aFrame, dirtyType, dirtyBits,
+ rootHandling);
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ } while (aFrame);
+}
+
+// Get the next sibling which might have a frame. This only considers siblings
+// that stylo post-traversal looks at, so only elements and text. In
+// particular, it ignores comments.
+static nsIContent* NextSiblingWhichMayHaveFrame(nsIContent* aContent) {
+ for (nsIContent* next = aContent->GetNextSibling(); next;
+ next = next->GetNextSibling()) {
+ if (next->IsElement() || next->IsText()) {
+ return next;
+ }
+ }
+
+ return nullptr;
+}
+
+// If |aFrame| is dirty or has dirty children, then we can skip updating
+// overflows since that will happen when it's reflowed.
+static inline bool CanSkipOverflowUpdates(const nsIFrame* aFrame) {
+ return aFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY |
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+}
+
+static inline void TryToDealWithScrollbarChange(nsChangeHint& aHint,
+ nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsPresContext* aPc) {
+ if (!(aHint & nsChangeHint_ScrollbarChange)) {
+ return;
+ }
+ aHint &= ~nsChangeHint_ScrollbarChange;
+ if (aHint & nsChangeHint_ReconstructFrame) {
+ return;
+ }
+
+ MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
+
+ const bool isRoot = aContent->IsInUncomposedDoc() && !aContent->GetParent();
+
+ // Only bother with this if we're the root or the body element, since:
+ // (a) It'd be *expensive* to reframe these particular nodes. They're
+ // at the root, so reframing would mean rebuilding the world.
+ // (b) It's often *unnecessary* to reframe for "overflow" changes on
+ // these particular nodes. In general, the only reason we reframe
+ // for "overflow" changes is so we can construct (or destroy) a
+ // scrollframe & scrollbars -- and the html/body nodes often don't
+ // need their own scrollframe/scrollbars because they coopt the ones
+ // on the viewport (which always exist). So depending on whether
+ // that's happening, we can skip the reframe for these nodes.
+ if (isRoot || aContent->IsHTMLElement(nsGkAtoms::body)) {
+ // If the restyled element provided/provides the scrollbar styles for
+ // the viewport before and/or after this restyle, AND it's not coopting
+ // that responsibility from some other element (which would need
+ // reconstruction to make its own scrollframe now), THEN: we don't need
+ // to reconstruct - we can just reflow, because no scrollframe is being
+ // added/removed.
+ Element* prevOverride = aPc->GetViewportScrollStylesOverrideElement();
+ Element* newOverride = aPc->UpdateViewportScrollStylesOverride();
+
+ const auto ProvidesScrollbarStyles = [&](nsIContent* aOverride) {
+ if (aOverride) {
+ return aOverride == aContent;
+ }
+ return isRoot;
+ };
+
+ if (ProvidesScrollbarStyles(prevOverride) ||
+ ProvidesScrollbarStyles(newOverride)) {
+ // If we get here, the restyled element provided the scrollbar styles
+ // for viewport before this restyle, OR it will provide them after.
+ if (!prevOverride || !newOverride || prevOverride == newOverride) {
+ // If we get here, the restyled element is NOT replacing (or being
+ // replaced by) some other element as the viewport's
+ // scrollbar-styles provider. (If it were, we'd potentially need to
+ // reframe to create a dedicated scrollframe for whichever element
+ // is being booted from providing viewport scrollbar styles.)
+ //
+ // Under these conditions, we're OK to assume that this "overflow"
+ // change only impacts the root viewport's scrollframe, which
+ // already exists, so we can simply reflow instead of reframing.
+ if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
+ sf->MarkScrollbarsDirtyForReflow();
+ } else if (nsIScrollableFrame* sf =
+ aPc->PresShell()->GetRootScrollFrameAsScrollable()) {
+ sf->MarkScrollbarsDirtyForReflow();
+ }
+ aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
+ } else {
+ // If we changed the override element, we need to reconstruct as the old
+ // override element might start / stop being scrollable.
+ aHint |= nsChangeHint_ReconstructFrame;
+ }
+ return;
+ }
+ }
+
+ const bool scrollable = aFrame->StyleDisplay()->IsScrollableOverflow();
+ if (nsIScrollableFrame* sf = do_QueryFrame(aFrame)) {
+ if (scrollable && sf->HasAllNeededScrollbars()) {
+ sf->MarkScrollbarsDirtyForReflow();
+ // Once we've created scrollbars for a frame, don't bother reconstructing
+ // it just to remove them if we still need a scroll frame.
+ aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
+ return;
+ }
+ } else if (aFrame->IsTextInputFrame()) {
+ // input / textarea for the most part don't honor overflow themselves, the
+ // editor root will deal with the change if needed.
+ // However the textarea intrinsic size relies on GetDesiredScrollbarSizes(),
+ // so we need to reflow the textarea itself, not just the inner control.
+ aHint |= nsChangeHint_ReflowHintsForScrollbarChange;
+ return;
+ } else if (!scrollable) {
+ // Something changed, but we don't have nor will have a scroll frame,
+ // there's nothing to do here.
+ return;
+ }
+
+ // Oh well, we couldn't optimize it out, just reconstruct frames for the
+ // subtree.
+ aHint |= nsChangeHint_ReconstructFrame;
+}
+
+static void TryToHandleContainingBlockChange(nsChangeHint& aHint,
+ nsIFrame* aFrame) {
+ if (!(aHint & nsChangeHint_UpdateContainingBlock)) {
+ return;
+ }
+ if (aHint & nsChangeHint_ReconstructFrame) {
+ return;
+ }
+ MOZ_ASSERT(aFrame, "If we're not reframing, we ought to have a frame");
+ nsIFrame* containingBlock = ContainingBlockForFrame(aFrame);
+ if (!containingBlock ||
+ NeedToReframeToUpdateContainingBlock(aFrame, containingBlock)) {
+ // The frame has positioned children that need to be reparented, or it can't
+ // easily be converted to/from being an abs-pos container correctly.
+ aHint |= nsChangeHint_ReconstructFrame;
+ return;
+ }
+ const bool isCb = aFrame->IsAbsPosContainingBlock();
+
+ // The absolute container should be containingBlock.
+ for (nsIFrame* cont = containingBlock; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ // Normally frame construction would set state bits as needed,
+ // but we're not going to reconstruct the frame so we need to set
+ // them. It's because we need to set this state on each affected frame
+ // that we can't coalesce nsChangeHint_UpdateContainingBlock hints up
+ // to ancestors (i.e. it can't be an change hint that is handled for
+ // descendants).
+ if (isCb) {
+ if (!cont->IsAbsoluteContainer() &&
+ cont->HasAnyStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN)) {
+ cont->MarkAsAbsoluteContainingBlock();
+ }
+ } else if (cont->IsAbsoluteContainer()) {
+ if (cont->HasAbsolutelyPositionedChildren()) {
+ // If |cont| still has absolutely positioned children,
+ // we can't call MarkAsNotAbsoluteContainingBlock. This
+ // will remove a frame list that still has children in
+ // it that we need to keep track of.
+ // The optimization of removing it isn't particularly
+ // important, although it does mean we skip some tests.
+ NS_WARNING("skipping removal of absolute containing block");
+ } else {
+ cont->MarkAsNotAbsoluteContainingBlock();
+ }
+ }
+ }
+}
+
+void RestyleManager::ProcessRestyledFrames(nsStyleChangeList& aChangeList) {
+ NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+ "Someone forgot a script blocker");
+
+ // See bug 1378219 comment 9:
+ // Recursive calls here are a bit worrying, but apparently do happen in the
+ // wild (although not currently in any of our automated tests). Try to get a
+ // stack from Nightly/Dev channel to figure out what's going on and whether
+ // it's OK.
+ MOZ_DIAGNOSTIC_ASSERT(!mDestroyedFrames, "ProcessRestyledFrames recursion");
+
+ if (aChangeList.IsEmpty()) {
+ return;
+ }
+
+ // If mDestroyedFrames is null, we want to create a new hashtable here
+ // and destroy it on exit; but if it is already non-null (because we're in
+ // a recursive call), we will continue to use the existing table to
+ // accumulate destroyed frames, and NOT clear mDestroyedFrames on exit.
+ // We use a MaybeClearDestroyedFrames helper to conditionally reset the
+ // mDestroyedFrames pointer when this method returns.
+ typedef decltype(mDestroyedFrames) DestroyedFramesT;
+ class MOZ_RAII MaybeClearDestroyedFrames {
+ private:
+ DestroyedFramesT& mDestroyedFramesRef; // ref to caller's mDestroyedFrames
+ const bool mResetOnDestruction;
+
+ public:
+ explicit MaybeClearDestroyedFrames(DestroyedFramesT& aTarget)
+ : mDestroyedFramesRef(aTarget),
+ mResetOnDestruction(!aTarget) // reset only if target starts out null
+ {}
+ ~MaybeClearDestroyedFrames() {
+ if (mResetOnDestruction) {
+ mDestroyedFramesRef.reset(nullptr);
+ }
+ }
+ };
+
+ MaybeClearDestroyedFrames maybeClear(mDestroyedFrames);
+ if (!mDestroyedFrames) {
+ mDestroyedFrames = MakeUnique<nsTHashSet<const nsIFrame*>>();
+ }
+
+ AUTO_PROFILER_LABEL("RestyleManager::ProcessRestyledFrames", LAYOUT);
+
+ nsPresContext* presContext = PresContext();
+ nsCSSFrameConstructor* frameConstructor = presContext->FrameConstructor();
+
+ bool didUpdateCursor = false;
+
+ for (size_t i = 0; i < aChangeList.Length(); ++i) {
+ // Collect and coalesce adjacent siblings for lazy frame construction.
+ // Eventually it would be even better to make RecreateFramesForContent
+ // accept a range and coalesce all adjacent reconstructs (bug 1344139).
+ size_t lazyRangeStart = i;
+ while (i < aChangeList.Length() && aChangeList[i].mContent &&
+ aChangeList[i].mContent->HasFlag(NODE_NEEDS_FRAME) &&
+ (i == lazyRangeStart ||
+ NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent) ==
+ aChangeList[i].mContent)) {
+ MOZ_ASSERT(aChangeList[i].mHint & nsChangeHint_ReconstructFrame);
+ MOZ_ASSERT(!aChangeList[i].mFrame);
+ ++i;
+ }
+ if (i != lazyRangeStart) {
+ nsIContent* start = aChangeList[lazyRangeStart].mContent;
+ nsIContent* end =
+ NextSiblingWhichMayHaveFrame(aChangeList[i - 1].mContent);
+ if (!end) {
+ frameConstructor->ContentAppended(
+ start, nsCSSFrameConstructor::InsertionKind::Sync);
+ } else {
+ frameConstructor->ContentRangeInserted(
+ start, end, nsCSSFrameConstructor::InsertionKind::Sync);
+ }
+ }
+ for (size_t j = lazyRangeStart; j < i; ++j) {
+ MOZ_ASSERT(!aChangeList[j].mContent->GetPrimaryFrame() ||
+ !aChangeList[j].mContent->HasFlag(NODE_NEEDS_FRAME));
+ }
+ if (i == aChangeList.Length()) {
+ break;
+ }
+
+ const nsStyleChangeData& data = aChangeList[i];
+ nsIFrame* frame = data.mFrame;
+ nsIContent* content = data.mContent;
+ nsChangeHint hint = data.mHint;
+ bool didReflowThisFrame = false;
+
+ NS_ASSERTION(!(hint & nsChangeHint_AllReflowHints) ||
+ (hint & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ // skip any frame that has been destroyed due to a ripple effect
+ if (frame && mDestroyedFrames->Contains(frame)) {
+ continue;
+ }
+
+ if (frame && frame->GetContent() != content) {
+ // XXXbz this is due to image maps messing with the primary frame of
+ // <area>s. See bug 135040. Remove this block once that's fixed.
+ frame = nullptr;
+ if (!(hint & nsChangeHint_ReconstructFrame)) {
+ continue;
+ }
+ }
+
+ TryToDealWithScrollbarChange(hint, content, frame, presContext);
+ TryToHandleContainingBlockChange(hint, frame);
+
+ if (hint & nsChangeHint_ReconstructFrame) {
+ // If we ever start passing true here, be careful of restyles
+ // that involve a reframe and animations. In particular, if the
+ // restyle we're processing here is an animation restyle, but
+ // the style resolution we will do for the frame construction
+ // happens async when we're not in an animation restyle already,
+ // problems could arise.
+ // We could also have problems with triggering of CSS transitions
+ // on elements whose frames are reconstructed, since we depend on
+ // the reconstruction happening synchronously.
+ frameConstructor->RecreateFramesForContent(
+ content, nsCSSFrameConstructor::InsertionKind::Sync);
+ continue;
+ }
+
+ MOZ_ASSERT(frame, "This shouldn't happen");
+ if (hint & nsChangeHint_AddOrRemoveTransform) {
+ for (nsIFrame* cont = frame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ if (cont->StyleDisplay()->HasTransform(cont)) {
+ cont->AddStateBits(NS_FRAME_MAY_BE_TRANSFORMED);
+ }
+ // Don't remove NS_FRAME_MAY_BE_TRANSFORMED since it may still be
+ // transformed by other means. It's OK to have the bit even if it's
+ // not needed.
+ }
+ // When dropping a running transform animation we will first add an
+ // nsChangeHint_UpdateTransformLayer hint as part of the animation-only
+ // restyle. During the subsequent regular restyle, if the animation was
+ // the only reason the element had any transform applied, we will add
+ // nsChangeHint_AddOrRemoveTransform as part of the regular restyle.
+ //
+ // With the Gecko backend, these two change hints are processed
+ // after each restyle but when using the Servo backend they accumulate
+ // and are processed together after we have already removed the
+ // transform as part of the regular restyle. Since we don't actually
+ // need the nsChangeHint_UpdateTransformLayer hint if we already have
+ // a nsChangeHint_AddOrRemoveTransform hint, and since we
+ // will fail an assertion in ApplyRenderingChangeToTree if we try
+ // specify nsChangeHint_UpdateTransformLayer but don't have any
+ // transform style, we just drop the unneeded hint here.
+ hint &= ~nsChangeHint_UpdateTransformLayer;
+ }
+
+ if (!frame->FrameMaintainsOverflow()) {
+ // frame does not maintain overflow rects, so avoid calling
+ // FinishAndStoreOverflow on it:
+ hint &=
+ ~(nsChangeHint_UpdateOverflow | nsChangeHint_ChildrenOnlyTransform |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateParentOverflow |
+ nsChangeHint_UpdateSubtreeOverflow);
+ }
+
+ if (!frame->HasAnyStateBits(NS_FRAME_MAY_BE_TRANSFORMED)) {
+ // Frame can not be transformed, and thus a change in transform will
+ // have no effect and we should not use either
+ // nsChangeHint_UpdatePostTransformOverflow or
+ // nsChangeHint_UpdateTransformLayerhint.
+ hint &= ~(nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateTransformLayer);
+ }
+
+ if ((hint & nsChangeHint_UpdateEffects) &&
+ frame == nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame)) {
+ SVGObserverUtils::UpdateEffects(frame);
+ }
+ if ((hint & nsChangeHint_InvalidateRenderingObservers) ||
+ ((hint & nsChangeHint_UpdateOpacityLayer) &&
+ frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT))) {
+ SVGObserverUtils::InvalidateRenderingObservers(frame);
+ frame->SchedulePaint();
+ }
+ if (hint & nsChangeHint_NeedReflow) {
+ StyleChangeReflow(frame, hint);
+ didReflowThisFrame = true;
+ }
+
+ // Here we need to propagate repaint frame change hint instead of update
+ // opacity layer change hint when we do opacity optimization for SVG.
+ // We can't do it in nsStyleEffects::CalcDifference() just like we do
+ // for the optimization for 0.99 over opacity values since we have no way
+ // to call SVGUtils::CanOptimizeOpacity() there.
+ if ((hint & nsChangeHint_UpdateOpacityLayer) &&
+ SVGUtils::CanOptimizeOpacity(frame)) {
+ hint &= ~nsChangeHint_UpdateOpacityLayer;
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ if ((hint & nsChangeHint_UpdateUsesOpacity) && frame->IsTablePart()) {
+ NS_ASSERTION(hint & nsChangeHint_UpdateOpacityLayer,
+ "should only return UpdateUsesOpacity hint "
+ "when also returning UpdateOpacityLayer hint");
+ // When an internal table part (including cells) changes between
+ // having opacity 1 and non-1, it changes whether its
+ // backgrounds (and those of table parts inside of it) are
+ // painted as part of the table's nsDisplayTableBorderBackground
+ // display item, or part of its own display item. That requires
+ // invalidation, so change UpdateOpacityLayer to RepaintFrame.
+ hint &= ~nsChangeHint_UpdateOpacityLayer;
+ hint |= nsChangeHint_RepaintFrame;
+ }
+
+ // Opacity disables preserve-3d, so if we toggle it, then we also need
+ // to update the overflow areas of all potentially affected frames.
+ if ((hint & nsChangeHint_UpdateUsesOpacity) &&
+ frame->StyleDisplay()->mTransformStyle ==
+ StyleTransformStyle::Preserve3d) {
+ hint |= nsChangeHint_UpdateSubtreeOverflow;
+ }
+
+ if (hint & nsChangeHint_UpdateBackgroundPosition) {
+ // For most frame types, DLBI can detect background position changes,
+ // so we only need to schedule a paint.
+ hint |= nsChangeHint_SchedulePaint;
+ if (frame->IsTablePart() || frame->IsMathMLFrame()) {
+ // Table parts and MathML frames don't build display items for their
+ // backgrounds, so DLBI can't detect background-position changes for
+ // these frames. Repaint the whole frame.
+ hint |= nsChangeHint_RepaintFrame;
+ }
+ }
+
+ if (hint &
+ (nsChangeHint_RepaintFrame | nsChangeHint_UpdateOpacityLayer |
+ nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_ChildrenOnlyTransform | nsChangeHint_SchedulePaint)) {
+ ApplyRenderingChangeToTree(presContext->PresShell(), frame, hint);
+ }
+
+ if (hint & (nsChangeHint_UpdateTransformLayer |
+ nsChangeHint_AddOrRemoveTransform)) {
+ // We need to trigger re-snapping to this content if we snapped to the
+ // content on the last scroll operation.
+ ScrollSnapUtils::PostPendingResnapIfNeededFor(frame);
+ }
+
+ if ((hint & nsChangeHint_RecomputePosition) && !didReflowThisFrame) {
+ // It is possible for this to fall back to a reflow
+ if (!RecomputePosition(frame)) {
+ StyleChangeReflow(frame, nsChangeHint_NeedReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition);
+ didReflowThisFrame = true;
+ }
+ }
+ NS_ASSERTION(!(hint & nsChangeHint_ChildrenOnlyTransform) ||
+ (hint & nsChangeHint_UpdateOverflow),
+ "nsChangeHint_UpdateOverflow should be passed too");
+ if (!didReflowThisFrame &&
+ (hint & (nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow |
+ nsChangeHint_UpdateParentOverflow |
+ nsChangeHint_UpdateSubtreeOverflow))) {
+ if (hint & nsChangeHint_UpdateSubtreeOverflow) {
+ for (nsIFrame* cont = frame; cont;
+ cont = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ AddSubtreeToOverflowTracker(cont, mOverflowChangedTracker);
+ }
+ // The work we just did in AddSubtreeToOverflowTracker
+ // subsumes some of the other hints:
+ hint &= ~(nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow);
+ }
+ if (hint & nsChangeHint_ChildrenOnlyTransform) {
+ // We need to update overflows. The correct frame(s) to update depends
+ // on whether the ChangeHint came from an outer or an inner svg.
+ nsIFrame* hintFrame = GetFrameForChildrenOnlyTransformHint(frame);
+ NS_ASSERTION(!nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ NS_ASSERTION(
+ !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(hintFrame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ if (hintFrame->IsSVGOuterSVGAnonChildFrame()) {
+ // The children only transform of an outer svg frame is applied to
+ // the outer svg's anonymous child frame (instead of to the
+ // anonymous child's children).
+
+ if (!CanSkipOverflowUpdates(hintFrame)) {
+ mOverflowChangedTracker.AddFrame(
+ hintFrame, OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ } else {
+ // The children only transform is applied to the child frames of an
+ // inner svg frame, so update the child overflows.
+ nsIFrame* childFrame = hintFrame->PrincipalChildList().FirstChild();
+ for (; childFrame; childFrame = childFrame->GetNextSibling()) {
+ MOZ_ASSERT(childFrame->IsSVGFrame(),
+ "Not expecting non-SVG children");
+ if (!CanSkipOverflowUpdates(childFrame)) {
+ mOverflowChangedTracker.AddFrame(
+ childFrame, OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ NS_ASSERTION(
+ !nsLayoutUtils::GetNextContinuationOrIBSplitSibling(childFrame),
+ "SVG frames should not have continuations "
+ "or ib-split siblings");
+ NS_ASSERTION(
+ childFrame->GetParent() == hintFrame,
+ "SVG child frame not expected to have different parent");
+ }
+ }
+ }
+ if (!CanSkipOverflowUpdates(frame)) {
+ if (hint & (nsChangeHint_UpdateOverflow |
+ nsChangeHint_UpdatePostTransformOverflow)) {
+ OverflowChangedTracker::ChangeKind changeKind;
+ // If we have both nsChangeHint_UpdateOverflow and
+ // nsChangeHint_UpdatePostTransformOverflow,
+ // CHILDREN_CHANGED is selected as it is
+ // strictly stronger.
+ if (hint & nsChangeHint_UpdateOverflow) {
+ changeKind = OverflowChangedTracker::CHILDREN_CHANGED;
+ } else {
+ changeKind = OverflowChangedTracker::TRANSFORM_CHANGED;
+ }
+ for (nsIFrame* cont = frame; cont;
+ cont =
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ mOverflowChangedTracker.AddFrame(cont, changeKind);
+ }
+ }
+ // UpdateParentOverflow hints need to be processed in addition
+ // to the above, since if the processing of the above hints
+ // yields no change, the update will not propagate to the
+ // parent.
+ if (hint & nsChangeHint_UpdateParentOverflow) {
+ MOZ_ASSERT(frame->GetParent(),
+ "shouldn't get style hints for the root frame");
+ for (nsIFrame* cont = frame; cont;
+ cont =
+ nsLayoutUtils::GetNextContinuationOrIBSplitSibling(cont)) {
+ mOverflowChangedTracker.AddFrame(
+ cont->GetParent(), OverflowChangedTracker::CHILDREN_CHANGED);
+ }
+ }
+ }
+ }
+ if ((hint & nsChangeHint_UpdateCursor) && !didUpdateCursor) {
+ presContext->PresShell()->SynthesizeMouseMove(false);
+ didUpdateCursor = true;
+ }
+ if (hint & nsChangeHint_UpdateTableCellSpans) {
+ frameConstructor->UpdateTableCellSpans(content);
+ }
+ if (hint & nsChangeHint_VisibilityChange) {
+ frame->UpdateVisibleDescendantsState();
+ }
+ }
+
+ aChangeList.Clear();
+ FlushOverflowChangedTracker();
+}
+
+/* static */
+uint64_t RestyleManager::GetAnimationGenerationForFrame(nsIFrame* aStyleFrame) {
+ EffectSet* effectSet = EffectSet::GetForStyleFrame(aStyleFrame);
+ return effectSet ? effectSet->GetAnimationGeneration() : 0;
+}
+
+void RestyleManager::IncrementAnimationGeneration() {
+ // We update the animation generation at start of each call to
+ // ProcessPendingRestyles so we should ignore any subsequent (redundant)
+ // calls that occur while we are still processing restyles.
+ if (!mInStyleRefresh) {
+ ++mAnimationGeneration;
+ }
+}
+
+/* static */
+void RestyleManager::AddLayerChangesForAnimation(
+ nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement,
+ nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess) {
+ MOZ_ASSERT(aElement);
+ MOZ_ASSERT(!!aStyleFrame == !!aPrimaryFrame);
+ if (!aStyleFrame) {
+ return;
+ }
+
+ uint64_t frameGeneration =
+ RestyleManager::GetAnimationGenerationForFrame(aStyleFrame);
+
+ Maybe<nsCSSPropertyIDSet> effectiveAnimationProperties;
+
+ nsChangeHint hint = nsChangeHint(0);
+ auto maybeApplyChangeHint = [&](const Maybe<uint64_t>& aGeneration,
+ DisplayItemType aDisplayItemType) -> bool {
+ if (aGeneration && frameGeneration != *aGeneration) {
+ // If we have a transform layer but don't have any transform style, we
+ // probably just removed the transform but haven't destroyed the layer
+ // yet. In this case we will typically add the appropriate change hint
+ // (nsChangeHint_UpdateContainingBlock) when we compare styles so in
+ // theory we could skip adding any change hint here.
+ //
+ // However, sometimes when we compare styles we'll get no change. For
+ // example, if the transform style was 'none' when we sent the transform
+ // animation to the compositor and the current transform style is now
+ // 'none' we'll think nothing changed but actually we still need to
+ // trigger an update to clear whatever style the transform animation set
+ // on the compositor. To handle this case we simply set all the change
+ // hints relevant to removing transform style (since we don't know exactly
+ // what changes happened while the animation was running on the
+ // compositor).
+ //
+ // Note that we *don't* add nsChangeHint_UpdateTransformLayer since if we
+ // did, ApplyRenderingChangeToTree would complain that we're updating a
+ // transform layer without a transform.
+ if (aDisplayItemType == DisplayItemType::TYPE_TRANSFORM &&
+ !aStyleFrame->StyleDisplay()->HasTransformStyle()) {
+ // Add all the hints for a removing a transform if they are not already
+ // set for this frame.
+ if (!(NS_IsHintSubset(nsChangeHint_ComprehensiveAddOrRemoveTransform,
+ aHintForThisFrame))) {
+ hint |= nsChangeHint_ComprehensiveAddOrRemoveTransform;
+ }
+ return true;
+ }
+ hint |= LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
+ }
+
+ // We consider it's the first paint for the frame if we have an animation
+ // for the property but have no layer, for the case of WebRender, no
+ // corresponding animation info.
+ // Note that in case of animations which has properties preventing running
+ // on the compositor, e.g., width or height, corresponding layer is not
+ // created at all, but even in such cases, we normally set valid change
+ // hint for such animations in each tick, i.e. restyles in each tick. As
+ // a result, we usually do restyles for such animations in every tick on
+ // the main-thread. The only animations which will be affected by this
+ // explicit change hint are animations that have opacity/transform but did
+ // not have those properies just before. e.g, setting transform by
+ // setKeyframes or changing target element from other target which prevents
+ // running on the compositor, etc.
+ if (!aGeneration) {
+ nsChangeHint hintForDisplayItem =
+ LayerAnimationInfo::GetChangeHintFor(aDisplayItemType);
+ // We don't need to apply the corresponding change hint if we already have
+ // it.
+ if (NS_IsHintSubset(hintForDisplayItem, aHintForThisFrame)) {
+ return true;
+ }
+
+ if (!effectiveAnimationProperties) {
+ effectiveAnimationProperties.emplace(
+ nsLayoutUtils::GetAnimationPropertiesForCompositor(aStyleFrame));
+ }
+ const nsCSSPropertyIDSet& propertiesForDisplayItem =
+ LayerAnimationInfo::GetCSSPropertiesFor(aDisplayItemType);
+ if (effectiveAnimationProperties->Intersects(propertiesForDisplayItem)) {
+ hint |= hintForDisplayItem;
+ }
+ }
+ return true;
+ };
+
+ AnimationInfo::EnumerateGenerationOnFrame(
+ aStyleFrame, aElement, LayerAnimationInfo::sDisplayItemTypes,
+ maybeApplyChangeHint);
+
+ if (hint) {
+ // We apply the hint to the primary frame, not the style frame. Transform
+ // and opacity hints apply to the table wrapper box, not the table box.
+ aChangeListToProcess.AppendChange(aPrimaryFrame, aElement, hint);
+ }
+}
+
+RestyleManager::AnimationsWithDestroyedFrame::AnimationsWithDestroyedFrame(
+ RestyleManager* aRestyleManager)
+ : mRestyleManager(aRestyleManager),
+ mRestorePointer(mRestyleManager->mAnimationsWithDestroyedFrame) {
+ MOZ_ASSERT(!mRestyleManager->mAnimationsWithDestroyedFrame,
+ "shouldn't construct recursively");
+ mRestyleManager->mAnimationsWithDestroyedFrame = this;
+}
+
+void RestyleManager::AnimationsWithDestroyedFrame ::
+ StopAnimationsForElementsWithoutFrames() {
+ StopAnimationsWithoutFrame(mContents, PseudoStyleType::NotPseudo);
+ StopAnimationsWithoutFrame(mBeforeContents, PseudoStyleType::before);
+ StopAnimationsWithoutFrame(mAfterContents, PseudoStyleType::after);
+ StopAnimationsWithoutFrame(mMarkerContents, PseudoStyleType::marker);
+}
+
+void RestyleManager::AnimationsWithDestroyedFrame ::StopAnimationsWithoutFrame(
+ nsTArray<RefPtr<nsIContent>>& aArray, PseudoStyleType aPseudoType) {
+ nsAnimationManager* animationManager =
+ mRestyleManager->PresContext()->AnimationManager();
+ nsTransitionManager* transitionManager =
+ mRestyleManager->PresContext()->TransitionManager();
+ for (nsIContent* content : aArray) {
+ if (aPseudoType == PseudoStyleType::NotPseudo) {
+ if (content->GetPrimaryFrame()) {
+ continue;
+ }
+ } else if (aPseudoType == PseudoStyleType::before) {
+ if (nsLayoutUtils::GetBeforeFrame(content)) {
+ continue;
+ }
+ } else if (aPseudoType == PseudoStyleType::after) {
+ if (nsLayoutUtils::GetAfterFrame(content)) {
+ continue;
+ }
+ } else if (aPseudoType == PseudoStyleType::marker) {
+ if (nsLayoutUtils::GetMarkerFrame(content)) {
+ continue;
+ }
+ }
+ dom::Element* element = content->AsElement();
+
+ animationManager->StopAnimationsForElement(element, aPseudoType);
+ transitionManager->StopAnimationsForElement(element, aPseudoType);
+
+ // All other animations should keep running but not running on the
+ // *compositor* at this point.
+ if (EffectSet* effectSet = EffectSet::Get(element, aPseudoType)) {
+ for (KeyframeEffect* effect : *effectSet) {
+ effect->ResetIsRunningOnCompositor();
+ }
+ }
+ }
+}
+
+#ifdef DEBUG
+static bool IsAnonBox(const nsIFrame* aFrame) {
+ return aFrame->Style()->IsAnonBox();
+}
+
+static const nsIFrame* FirstContinuationOrPartOfIBSplit(
+ const nsIFrame* aFrame) {
+ if (!aFrame) {
+ return nullptr;
+ }
+
+ return nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
+}
+
+static const nsIFrame* ExpectedOwnerForChild(const nsIFrame* aFrame) {
+ const nsIFrame* parent = aFrame->GetParent();
+ if (aFrame->IsTableFrame()) {
+ MOZ_ASSERT(parent->IsTableWrapperFrame());
+ parent = parent->GetParent();
+ }
+
+ if (IsAnonBox(aFrame) && !aFrame->IsTextFrame()) {
+ if (parent->IsLineFrame()) {
+ parent = parent->GetParent();
+ }
+ return parent->IsViewportFrame() ? nullptr
+ : FirstContinuationOrPartOfIBSplit(parent);
+ }
+
+ if (aFrame->IsLineFrame()) {
+ // A ::first-line always ends up here via its block, which is therefore the
+ // right expected owner. That block can be an
+ // anonymous box. For example, we could have a ::first-line on a columnated
+ // block; the blockframe is the column-content anonymous box in that case.
+ // So we don't want to end up in the code below, which steps out of anon
+ // boxes. Just return the parent of the line frame, which is the block.
+ return parent;
+ }
+
+ if (aFrame->IsLetterFrame()) {
+ // Ditto for ::first-letter. A first-letter always arrives here via its
+ // direct parent, except when it's parented to a ::first-line.
+ if (parent->IsLineFrame()) {
+ parent = parent->GetParent();
+ }
+ return FirstContinuationOrPartOfIBSplit(parent);
+ }
+
+ if (parent->IsLetterFrame()) {
+ // Things never have ::first-letter as their expected parent. Go
+ // on up to the ::first-letter's parent.
+ parent = parent->GetParent();
+ }
+
+ parent = FirstContinuationOrPartOfIBSplit(parent);
+
+ // We've handled already anon boxes, so now we're looking at
+ // a frame of a DOM element or pseudo. Hop through anon and line-boxes
+ // generated by our DOM parent, and go find the owner frame for it.
+ while (parent && (IsAnonBox(parent) || parent->IsLineFrame())) {
+ auto pseudo = parent->Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::tableWrapper) {
+ const nsIFrame* tableFrame = parent->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(tableFrame->IsTableFrame());
+ // Handle :-moz-table and :-moz-inline-table.
+ parent = IsAnonBox(tableFrame) ? parent->GetParent() : tableFrame;
+ } else {
+ // We get the in-flow parent here so that we can handle the OOF anonymous
+ // boxed to get the correct parent.
+ parent = parent->GetInFlowParent();
+ }
+ parent = FirstContinuationOrPartOfIBSplit(parent);
+ }
+
+ return parent;
+}
+
+// FIXME(emilio, bug 1633685): We should ideally figure out how to properly
+// restyle replicated fixed pos frames... We seem to assume everywhere that they
+// can't get restyled at the moment...
+static bool IsInReplicatedFixedPosTree(const nsIFrame* aFrame) {
+ if (!aFrame->PresContext()->IsPaginated()) {
+ return false;
+ }
+
+ for (; aFrame; aFrame = aFrame->GetParent()) {
+ if (aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
+ !aFrame->FirstContinuation()->IsPrimaryFrame() &&
+ nsLayoutUtils::IsReallyFixedPos(aFrame)) {
+ return true;
+ }
+ }
+
+ return true;
+}
+
+void ServoRestyleState::AssertOwner(const ServoRestyleState& aParent) const {
+ MOZ_ASSERT(mOwner);
+ MOZ_ASSERT(!mOwner->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+ MOZ_ASSERT(!mOwner->IsColumnSpanInMulticolSubtree());
+ // We allow aParent.mOwner to be null, for cases when we're not starting at
+ // the root of the tree. We also allow aParent.mOwner to be somewhere up our
+ // expected owner chain not our immediate owner, which allows us creating long
+ // chains of ServoRestyleStates in some cases where it's just not worth it.
+ if (aParent.mOwner) {
+ const nsIFrame* owner = ExpectedOwnerForChild(mOwner);
+ if (owner != aParent.mOwner && !IsInReplicatedFixedPosTree(mOwner)) {
+ MOZ_ASSERT(IsAnonBox(owner),
+ "Should only have expected owner weirdness when anon boxes "
+ "are involved");
+ bool found = false;
+ for (; owner; owner = ExpectedOwnerForChild(owner)) {
+ if (owner == aParent.mOwner) {
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found, "Must have aParent.mOwner on our expected owner chain");
+ }
+ }
+}
+
+nsChangeHint ServoRestyleState::ChangesHandledFor(
+ const nsIFrame* aFrame) const {
+ if (!mOwner) {
+ MOZ_ASSERT(!mChangesHandled);
+ return mChangesHandled;
+ }
+
+ MOZ_ASSERT(mOwner == ExpectedOwnerForChild(aFrame) ||
+ IsInReplicatedFixedPosTree(aFrame),
+ "Missed some frame in the hierarchy?");
+ return mChangesHandled;
+}
+#endif
+
+void ServoRestyleState::AddPendingWrapperRestyle(nsIFrame* aWrapperFrame) {
+ MOZ_ASSERT(aWrapperFrame->Style()->IsWrapperAnonBox(),
+ "All our wrappers are anon boxes, and why would we restyle "
+ "non-inheriting ones?");
+ MOZ_ASSERT(aWrapperFrame->Style()->IsInheritingAnonBox(),
+ "All our wrappers are anon boxes, and why would we restyle "
+ "non-inheriting ones?");
+ MOZ_ASSERT(
+ aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::cellContent,
+ "Someone should be using TableAwareParentFor");
+ MOZ_ASSERT(
+ aWrapperFrame->Style()->GetPseudoType() != PseudoStyleType::tableWrapper,
+ "Someone should be using TableAwareParentFor");
+ // Make sure we only add first continuations.
+ aWrapperFrame = aWrapperFrame->FirstContinuation();
+ nsIFrame* last = mPendingWrapperRestyles.SafeLastElement(nullptr);
+ if (last == aWrapperFrame) {
+ // Already queued up, nothing to do.
+ return;
+ }
+
+ // Make sure to queue up parents before children. But don't queue up
+ // ancestors of non-anonymous boxes here; those are handled when we traverse
+ // their non-anonymous kids.
+ if (aWrapperFrame->ParentIsWrapperAnonBox()) {
+ AddPendingWrapperRestyle(TableAwareParentFor(aWrapperFrame));
+ }
+
+ // If the append fails, we'll fail to restyle properly, but that's probably
+ // better than crashing.
+ if (mPendingWrapperRestyles.AppendElement(aWrapperFrame, fallible)) {
+ aWrapperFrame->SetIsWrapperAnonBoxNeedingRestyle(true);
+ }
+}
+
+void ServoRestyleState::ProcessWrapperRestyles(nsIFrame* aParentFrame) {
+ size_t i = mPendingWrapperRestyleOffset;
+ while (i < mPendingWrapperRestyles.Length()) {
+ i += ProcessMaybeNestedWrapperRestyle(aParentFrame, i);
+ }
+
+ mPendingWrapperRestyles.TruncateLength(mPendingWrapperRestyleOffset);
+}
+
+size_t ServoRestyleState::ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent,
+ size_t aIndex) {
+ // The frame at index aIndex is something we should restyle ourselves, but
+ // following frames may need separate ServoRestyleStates to restyle.
+ MOZ_ASSERT(aIndex < mPendingWrapperRestyles.Length());
+
+ nsIFrame* cur = mPendingWrapperRestyles[aIndex];
+ MOZ_ASSERT(cur->Style()->IsWrapperAnonBox());
+
+ // Where is cur supposed to inherit from? From its parent frame, except in
+ // the case when cur is a table, in which case it should be its grandparent.
+ // Also, not in the case when the resulting frame would be a first-line; in
+ // that case we should be inheriting from the block, and the first-line will
+ // do its fixup later if needed.
+ //
+ // Note that after we do all that fixup the parent we get might still not be
+ // aParent; for example aParent could be a scrollframe, in which case we
+ // should inherit from the scrollcontent frame. Or the parent might be some
+ // continuation of aParent.
+ //
+ // Try to assert as much as we can about the parent we actually end up using
+ // without triggering bogus asserts in all those various edge cases.
+ nsIFrame* parent = cur->GetParent();
+ if (cur->IsTableFrame()) {
+ MOZ_ASSERT(parent->IsTableWrapperFrame());
+ parent = parent->GetParent();
+ }
+ if (parent->IsLineFrame()) {
+ parent = parent->GetParent();
+ }
+ MOZ_ASSERT(FirstContinuationOrPartOfIBSplit(parent) == aParent ||
+ (parent->Style()->IsInheritingAnonBox() &&
+ parent->GetContent() == aParent->GetContent()));
+
+ // Now "this" is a ServoRestyleState for aParent, so if parent is not a next
+ // continuation (possibly across ib splits) of aParent we need a new
+ // ServoRestyleState for the kid.
+ Maybe<ServoRestyleState> parentRestyleState;
+ nsIFrame* parentForRestyle =
+ nsLayoutUtils::FirstContinuationOrIBSplitSibling(parent);
+ if (parentForRestyle != aParent) {
+ parentRestyleState.emplace(*parentForRestyle, *this, nsChangeHint_Empty,
+ Type::InFlow);
+ }
+ ServoRestyleState& curRestyleState =
+ parentRestyleState ? *parentRestyleState : *this;
+
+ // This frame may already have been restyled. Even if it has, we can't just
+ // return, because the next frame may be a kid of it that does need restyling.
+ if (cur->IsWrapperAnonBoxNeedingRestyle()) {
+ parentForRestyle->UpdateStyleOfChildAnonBox(cur, curRestyleState);
+ cur->SetIsWrapperAnonBoxNeedingRestyle(false);
+ }
+
+ size_t numProcessed = 1;
+
+ // Note: no overflow possible here, since aIndex < length.
+ if (aIndex + 1 < mPendingWrapperRestyles.Length()) {
+ nsIFrame* next = mPendingWrapperRestyles[aIndex + 1];
+ if (TableAwareParentFor(next) == cur &&
+ next->IsWrapperAnonBoxNeedingRestyle()) {
+ // It might be nice if we could do better than nsChangeHint_Empty. On
+ // the other hand, presumably our mChangesHandled already has the bits
+ // we really want here so in practice it doesn't matter.
+ ServoRestyleState childState(*cur, curRestyleState, nsChangeHint_Empty,
+ Type::InFlow,
+ /* aAssertWrapperRestyleLength = */ false);
+ numProcessed +=
+ childState.ProcessMaybeNestedWrapperRestyle(cur, aIndex + 1);
+ }
+ }
+
+ return numProcessed;
+}
+
+nsIFrame* ServoRestyleState::TableAwareParentFor(const nsIFrame* aChild) {
+ // We want to get the anon box parent for aChild. where aChild has
+ // ParentIsWrapperAnonBox().
+ //
+ // For the most part this is pretty straightforward, but there are two
+ // wrinkles. First, if aChild is a table, then we really want the parent of
+ // its table wrapper.
+ if (aChild->IsTableFrame()) {
+ aChild = aChild->GetParent();
+ MOZ_ASSERT(aChild->IsTableWrapperFrame());
+ }
+
+ nsIFrame* parent = aChild->GetParent();
+ // Now if parent is a cell-content frame, we actually want the cellframe.
+ if (parent->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
+ parent = parent->GetParent();
+ } else if (parent->IsTableWrapperFrame()) {
+ // Must be a caption. In that case we want the table here.
+ MOZ_ASSERT(aChild->StyleDisplay()->mDisplay == StyleDisplay::TableCaption);
+ parent = parent->PrincipalChildList().FirstChild();
+ }
+ return parent;
+}
+
+void RestyleManager::PostRestyleEvent(Element* aElement,
+ RestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint) {
+ MOZ_ASSERT(!(aMinChangeHint & nsChangeHint_NeutralChange),
+ "Didn't expect explicit change hints to be neutral!");
+ if (MOZ_UNLIKELY(IsDisconnected()) ||
+ MOZ_UNLIKELY(PresContext()->PresShell()->IsDestroying())) {
+ return;
+ }
+
+ // We allow posting restyles from within change hint handling, but not from
+ // within the restyle algorithm itself.
+ MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal());
+
+ if (!aRestyleHint && !aMinChangeHint) {
+ // FIXME(emilio): we should assert against this instead.
+ return; // Nothing to do.
+ }
+
+ // Assuming the restyle hints will invalidate cached style for
+ // getComputedStyle, since we don't know if any of the restyling that we do
+ // would affect undisplayed elements.
+ if (aRestyleHint) {
+ if (!(aRestyleHint & RestyleHint::ForAnimations())) {
+ mHaveNonAnimationRestyles = true;
+ }
+
+ IncrementUndisplayedRestyleGeneration();
+ }
+
+ // Processing change hints sometimes causes new change hints to be generated,
+ // and very occasionally, additional restyle hints. We collect the change
+ // hints manually to avoid re-traversing the DOM to find them.
+ if (mReentrantChanges && !aRestyleHint) {
+ mReentrantChanges->AppendElement(ReentrantChange{aElement, aMinChangeHint});
+ return;
+ }
+
+ if (aRestyleHint || aMinChangeHint) {
+ Servo_NoteExplicitHints(aElement, aRestyleHint, aMinChangeHint);
+ }
+}
+
+void RestyleManager::PostRestyleEventForAnimations(Element* aElement,
+ PseudoStyleType aPseudoType,
+ RestyleHint aRestyleHint) {
+ Element* elementToRestyle =
+ AnimationUtils::GetElementForRestyle(aElement, aPseudoType);
+
+ if (!elementToRestyle) {
+ // FIXME: Bug 1371107: When reframing happens,
+ // EffectCompositor::mElementsToRestyle still has unbound old pseudo
+ // element. We should drop it.
+ return;
+ }
+
+ mPresContext->TriggeredAnimationRestyle();
+
+ Servo_NoteExplicitHints(elementToRestyle, aRestyleHint, nsChangeHint(0));
+}
+
+void RestyleManager::RebuildAllStyleData(nsChangeHint aExtraHint,
+ RestyleHint aRestyleHint) {
+ // NOTE(emilio): The semantics of these methods are quite funny, in the sense
+ // that we're not supposed to need to rebuild the actual stylist data.
+ //
+ // That's handled as part of the MediumFeaturesChanged stuff, if needed.
+ //
+ // Clear the cached style data only if we are guaranteed to process the whole
+ // DOM tree again.
+ //
+ // FIXME(emilio): Decouple this, probably. This probably just wants to reset
+ // the "uses viewport units / uses rem" bits, and _maybe_ clear cached anon
+ // box styles and such... But it doesn't really always need to clear the
+ // initial style of the document and similar...
+ if (aRestyleHint.DefinitelyRecascadesAllSubtree()) {
+ StyleSet()->ClearCachedStyleData();
+ }
+
+ DocumentStyleRootIterator iter(mPresContext->Document());
+ while (Element* root = iter.GetNextStyleRoot()) {
+ PostRestyleEvent(root, aRestyleHint, aExtraHint);
+ }
+
+ // TODO(emilio, bz): Extensions can add/remove stylesheets that can affect
+ // non-inheriting anon boxes. It's not clear if we want to support that, but
+ // if we do, we need to re-selector-match them here.
+}
+
+/* static */
+void RestyleManager::ClearServoDataFromSubtree(Element* aElement,
+ IncludeRoot aIncludeRoot) {
+ if (aElement->HasServoData()) {
+ StyleChildrenIterator it(aElement);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsElement()) {
+ ClearServoDataFromSubtree(n->AsElement(), IncludeRoot::Yes);
+ }
+ }
+ }
+
+ if (MOZ_LIKELY(aIncludeRoot == IncludeRoot::Yes)) {
+ aElement->ClearServoData();
+ MOZ_ASSERT(!aElement->HasAnyOfFlags(Element::kAllServoDescendantBits |
+ NODE_NEEDS_FRAME));
+ MOZ_ASSERT(aElement != aElement->OwnerDoc()->GetServoRestyleRoot());
+ }
+}
+
+/* static */
+void RestyleManager::ClearRestyleStateFromSubtree(Element* aElement) {
+ if (aElement->HasAnyOfFlags(Element::kAllServoDescendantBits)) {
+ StyleChildrenIterator it(aElement);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (n->IsElement()) {
+ ClearRestyleStateFromSubtree(n->AsElement());
+ }
+ }
+ }
+
+ bool wasRestyled = false;
+ Unused << Servo_TakeChangeHint(aElement, &wasRestyled);
+ aElement->UnsetFlags(Element::kAllServoDescendantBits);
+}
+
+/**
+ * This struct takes care of encapsulating some common state that text nodes may
+ * need to track during the post-traversal.
+ *
+ * This is currently used to properly compute change hints when the parent
+ * element of this node is a display: contents node, and also to avoid computing
+ * the style for text children more than once per element.
+ */
+struct RestyleManager::TextPostTraversalState {
+ public:
+ TextPostTraversalState(Element& aParentElement, ComputedStyle* aParentContext,
+ bool aDisplayContentsParentStyleChanged,
+ ServoRestyleState& aParentRestyleState)
+ : mParentElement(aParentElement),
+ mParentContext(aParentContext),
+ mParentRestyleState(aParentRestyleState),
+ mStyle(nullptr),
+ mShouldPostHints(aDisplayContentsParentStyleChanged),
+ mShouldComputeHints(aDisplayContentsParentStyleChanged),
+ mComputedHint(nsChangeHint_Empty) {}
+
+ nsStyleChangeList& ChangeList() { return mParentRestyleState.ChangeList(); }
+
+ ComputedStyle& ComputeStyle(nsIContent* aTextNode) {
+ if (!mStyle) {
+ mStyle = mParentRestyleState.StyleSet().ResolveStyleForText(
+ aTextNode, &ParentStyle());
+ }
+ MOZ_ASSERT(mStyle);
+ return *mStyle;
+ }
+
+ void ComputeHintIfNeeded(nsIContent* aContent, nsIFrame* aTextFrame,
+ ComputedStyle& aNewStyle) {
+ MOZ_ASSERT(aTextFrame);
+ MOZ_ASSERT(aNewStyle.GetPseudoType() == PseudoStyleType::mozText);
+
+ if (MOZ_LIKELY(!mShouldPostHints)) {
+ return;
+ }
+
+ ComputedStyle* oldStyle = aTextFrame->Style();
+ MOZ_ASSERT(oldStyle->GetPseudoType() == PseudoStyleType::mozText);
+
+ // We rely on the fact that all the text children for the same element share
+ // style to avoid recomputing style differences for all of them.
+ //
+ // TODO(emilio): The above may not be true for ::first-{line,letter}, but
+ // we'll cross that bridge when we support those in stylo.
+ if (mShouldComputeHints) {
+ mShouldComputeHints = false;
+ uint32_t equalStructs;
+ mComputedHint = oldStyle->CalcStyleDifference(aNewStyle, &equalStructs);
+ mComputedHint = NS_RemoveSubsumedHints(
+ mComputedHint, mParentRestyleState.ChangesHandledFor(aTextFrame));
+ }
+
+ if (mComputedHint) {
+ mParentRestyleState.ChangeList().AppendChange(aTextFrame, aContent,
+ mComputedHint);
+ }
+ }
+
+ private:
+ ComputedStyle& ParentStyle() {
+ if (!mParentContext) {
+ mLazilyResolvedParentContext =
+ ServoStyleSet::ResolveServoStyle(mParentElement);
+ mParentContext = mLazilyResolvedParentContext;
+ }
+ return *mParentContext;
+ }
+
+ Element& mParentElement;
+ ComputedStyle* mParentContext;
+ RefPtr<ComputedStyle> mLazilyResolvedParentContext;
+ ServoRestyleState& mParentRestyleState;
+ RefPtr<ComputedStyle> mStyle;
+ bool mShouldPostHints;
+ bool mShouldComputeHints;
+ nsChangeHint mComputedHint;
+};
+
+static void UpdateBackdropIfNeeded(nsIFrame* aFrame, ServoStyleSet& aStyleSet,
+ nsStyleChangeList& aChangeList) {
+ const nsStyleDisplay* display = aFrame->Style()->StyleDisplay();
+ if (display->mTopLayer != StyleTopLayer::Top) {
+ return;
+ }
+
+ // Elements in the top layer are guaranteed to have absolute or fixed
+ // position per https://fullscreen.spec.whatwg.org/#new-stacking-layer.
+ MOZ_ASSERT(display->IsAbsolutelyPositionedStyle());
+
+ nsIFrame* backdropPlaceholder =
+ aFrame->GetChildList(FrameChildListID::Backdrop).FirstChild();
+ if (!backdropPlaceholder) {
+ return;
+ }
+
+ MOZ_ASSERT(backdropPlaceholder->IsPlaceholderFrame());
+ nsIFrame* backdropFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(backdropPlaceholder);
+ MOZ_ASSERT(backdropFrame->IsBackdropFrame());
+ MOZ_ASSERT(backdropFrame->Style()->GetPseudoType() ==
+ PseudoStyleType::backdrop);
+
+ RefPtr<ComputedStyle> newStyle = aStyleSet.ResolvePseudoElementStyle(
+ *aFrame->GetContent()->AsElement(), PseudoStyleType::backdrop, nullptr,
+ aFrame->Style());
+
+ // NOTE(emilio): We can't use the changes handled for the owner of the
+ // backdrop frame, since it's out of flow, and parented to the viewport or
+ // canvas frame (depending on the `position` value).
+ MOZ_ASSERT(backdropFrame->GetParent()->IsViewportFrame() ||
+ backdropFrame->GetParent()->IsCanvasFrame());
+ nsTArray<nsIFrame*> wrappersToRestyle;
+ nsTArray<RefPtr<Element>> anchorsToSuppress;
+ ServoRestyleState state(aStyleSet, aChangeList, wrappersToRestyle,
+ anchorsToSuppress);
+ nsIFrame::UpdateStyleOfOwnedChildFrame(backdropFrame, newStyle, state);
+ MOZ_ASSERT(anchorsToSuppress.IsEmpty());
+}
+
+static void UpdateFirstLetterIfNeeded(nsIFrame* aFrame,
+ ServoRestyleState& aRestyleState) {
+ MOZ_ASSERT(
+ !aFrame->IsBlockFrameOrSubclass(),
+ "You're probably duplicating work with UpdatePseudoElementStyles!");
+ if (!aFrame->HasFirstLetterChild()) {
+ return;
+ }
+
+ // We need to find the block the first-letter is associated with so we can
+ // find the right element for the first-letter's style resolution. Might as
+ // well just delegate the whole thing to that block.
+ nsIFrame* block = aFrame->GetParent();
+ while (!block->IsBlockFrameOrSubclass()) {
+ block = block->GetParent();
+ }
+
+ static_cast<nsBlockFrame*>(block->FirstContinuation())
+ ->UpdateFirstLetterStyle(aRestyleState);
+}
+
+static void UpdateOneAdditionalComputedStyle(nsIFrame* aFrame, uint32_t aIndex,
+ ComputedStyle& aOldContext,
+ ServoRestyleState& aRestyleState) {
+ auto pseudoType = aOldContext.GetPseudoType();
+ MOZ_ASSERT(pseudoType != PseudoStyleType::NotPseudo);
+ MOZ_ASSERT(
+ !nsCSSPseudoElements::PseudoElementSupportsUserActionState(pseudoType));
+
+ RefPtr<ComputedStyle> newStyle =
+ aRestyleState.StyleSet().ResolvePseudoElementStyle(
+ *aFrame->GetContent()->AsElement(), pseudoType, nullptr,
+ aFrame->Style());
+
+ uint32_t equalStructs; // Not used, actually.
+ nsChangeHint childHint =
+ aOldContext.CalcStyleDifference(*newStyle, &equalStructs);
+ if (!aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ !aFrame->IsColumnSpanInMulticolSubtree()) {
+ childHint = NS_RemoveSubsumedHints(childHint,
+ aRestyleState.ChangesHandledFor(aFrame));
+ }
+
+ 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(aFrame->GetContent());
+ }
+ aRestyleState.ChangeList().AppendChange(aFrame, aFrame->GetContent(),
+ childHint);
+ }
+
+ aFrame->SetAdditionalComputedStyle(aIndex, newStyle);
+}
+
+static void UpdateAdditionalComputedStyles(nsIFrame* aFrame,
+ ServoRestyleState& aRestyleState) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->GetContent() && aFrame->GetContent()->IsElement());
+
+ // FIXME(emilio): Consider adding a bit or something to avoid the initial
+ // virtual call?
+ uint32_t index = 0;
+ while (auto* oldStyle = aFrame->GetAdditionalComputedStyle(index)) {
+ UpdateOneAdditionalComputedStyle(aFrame, index++, *oldStyle, aRestyleState);
+ }
+}
+
+static void UpdateFramePseudoElementStyles(nsIFrame* aFrame,
+ ServoRestyleState& aRestyleState) {
+ if (nsBlockFrame* blockFrame = do_QueryFrame(aFrame)) {
+ blockFrame->UpdatePseudoElementStyles(aRestyleState);
+ } else {
+ UpdateFirstLetterIfNeeded(aFrame, aRestyleState);
+ }
+
+ UpdateBackdropIfNeeded(aFrame, aRestyleState.StyleSet(),
+ aRestyleState.ChangeList());
+}
+
+enum class ServoPostTraversalFlags : uint32_t {
+ Empty = 0,
+ // Whether parent was restyled.
+ ParentWasRestyled = 1 << 0,
+ // Skip sending accessibility notifications for all descendants.
+ SkipA11yNotifications = 1 << 1,
+ // Always send accessibility notifications if the element is shown.
+ // The SkipA11yNotifications flag above overrides this flag.
+ SendA11yNotificationsIfShown = 1 << 2,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ServoPostTraversalFlags)
+
+static bool IsVisibleForA11y(const ComputedStyle& aStyle) {
+ return aStyle.StyleVisibility()->IsVisible() && !aStyle.StyleUI()->IsInert();
+}
+
+static bool IsSubtreeVisibleForA11y(const ComputedStyle& aStyle) {
+ return aStyle.StyleDisplay()->mContentVisibility !=
+ StyleContentVisibility::Hidden;
+}
+
+// Send proper accessibility notifications and return post traversal
+// flags for kids.
+static ServoPostTraversalFlags SendA11yNotifications(
+ nsPresContext* aPresContext, Element* aElement,
+ const ComputedStyle& aOldStyle, const ComputedStyle& aNewStyle,
+ ServoPostTraversalFlags aFlags) {
+ using Flags = ServoPostTraversalFlags;
+ MOZ_ASSERT(!(aFlags & Flags::SkipA11yNotifications) ||
+ !(aFlags & Flags::SendA11yNotificationsIfShown),
+ "The two a11y flags should never be set together");
+
+#ifdef ACCESSIBILITY
+ nsAccessibilityService* accService = GetAccService();
+ if (!accService) {
+ // If we don't have accessibility service, accessibility is not
+ // enabled. Just skip everything.
+ return Flags::Empty;
+ }
+
+ if (aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually !=
+ aOldStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually) {
+ if (aElement->GetParent() &&
+ aElement->GetParent()->IsXULElement(nsGkAtoms::tabpanels)) {
+ accService->NotifyOfTabPanelVisibilityChange(
+ aPresContext->PresShell(), aElement,
+ aNewStyle.StyleUIReset()->mMozSubtreeHiddenOnlyVisually);
+ }
+ }
+
+ if (aFlags & Flags::SkipA11yNotifications) {
+ // Propagate the skipping flag to descendants.
+ return Flags::SkipA11yNotifications;
+ }
+
+ bool needsNotify = false;
+ const bool isVisible = IsVisibleForA11y(aNewStyle);
+ const bool wasVisible = IsVisibleForA11y(aOldStyle);
+
+ if (aFlags & Flags::SendA11yNotificationsIfShown) {
+ if (!isVisible) {
+ // Propagate the sending-if-shown flag to descendants.
+ return Flags::SendA11yNotificationsIfShown;
+ }
+ // We have asked accessibility service to remove the whole subtree
+ // of element which becomes invisible from the accessible tree, but
+ // this element is visible, so we need to add it back.
+ needsNotify = true;
+ } else {
+ // If we shouldn't skip in any case, we need to check whether our own
+ // visibility has changed.
+ // Also notify if the subtree visibility change due to content-visibility.
+ const bool isSubtreeVisible = IsSubtreeVisibleForA11y(aNewStyle);
+ const bool wasSubtreeVisible = IsSubtreeVisibleForA11y(aOldStyle);
+ needsNotify =
+ wasVisible != isVisible || wasSubtreeVisible != isSubtreeVisible;
+ }
+
+ if (needsNotify) {
+ PresShell* presShell = aPresContext->PresShell();
+ if (isVisible) {
+ accService->ContentRangeInserted(presShell, aElement,
+ aElement->GetNextSibling());
+ // We are adding the subtree. Accessibility service would handle
+ // descendants, so we should just skip them from notifying.
+ return Flags::SkipA11yNotifications;
+ }
+ if (wasVisible) {
+ // Remove the subtree of this invisible element, and ask any shown
+ // descendant to add themselves back.
+ accService->ContentRemoved(presShell, aElement);
+ return Flags::SendA11yNotificationsIfShown;
+ }
+ }
+#endif
+
+ return Flags::Empty;
+}
+
+bool RestyleManager::ProcessPostTraversal(Element* aElement,
+ ServoRestyleState& aRestyleState,
+ ServoPostTraversalFlags aFlags) {
+ nsIFrame* styleFrame = nsLayoutUtils::GetStyleFrame(aElement);
+ nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
+
+ MOZ_DIAGNOSTIC_ASSERT(aElement->HasServoData(),
+ "Element without Servo data on a post-traversal? How?");
+
+ // NOTE(emilio): This is needed because for table frames the bit is set on the
+ // table wrapper (which is the primary frame), not on the table itself.
+ const bool isOutOfFlow =
+ primaryFrame && primaryFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW);
+
+ // We need this because any column-spanner's parent frame is not its DOM
+ // parent's primary frame. We need some special check similar to out-of-flow
+ // frames.
+ const bool isColumnSpan =
+ primaryFrame && primaryFrame->IsColumnSpanInMulticolSubtree();
+
+ // Grab the change hint from Servo.
+ bool wasRestyled = false;
+ nsChangeHint changeHint =
+ static_cast<nsChangeHint>(Servo_TakeChangeHint(aElement, &wasRestyled));
+
+ RefPtr<ComputedStyle> upToDateStyleIfRestyled =
+ wasRestyled ? ServoStyleSet::ResolveServoStyle(*aElement) : nullptr;
+
+ // We should really fix the weird primary frame mapping for image maps
+ // (bug 135040)...
+ if (styleFrame && styleFrame->GetContent() != aElement) {
+ MOZ_ASSERT(styleFrame->IsImageFrameOrSubclass());
+ styleFrame = nullptr;
+ }
+
+ // Handle lazy frame construction by posting a reconstruct for any lazily-
+ // constructed roots.
+ if (aElement->HasFlag(NODE_NEEDS_FRAME)) {
+ changeHint |= nsChangeHint_ReconstructFrame;
+ MOZ_ASSERT(!styleFrame);
+ }
+
+ if (styleFrame) {
+ MOZ_ASSERT(primaryFrame);
+
+ nsIFrame* maybeAnonBoxChild;
+ if (isOutOfFlow) {
+ maybeAnonBoxChild = primaryFrame->GetPlaceholderFrame();
+ } else {
+ maybeAnonBoxChild = primaryFrame;
+ // Do not subsume change hints for the column-spanner.
+ if (!isColumnSpan) {
+ changeHint = NS_RemoveSubsumedHints(
+ changeHint, aRestyleState.ChangesHandledFor(styleFrame));
+ }
+ }
+
+ // If the parent wasn't restyled, the styles of our anon box parents won't
+ // change either.
+ if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
+ maybeAnonBoxChild->ParentIsWrapperAnonBox()) {
+ aRestyleState.AddPendingWrapperRestyle(
+ ServoRestyleState::TableAwareParentFor(maybeAnonBoxChild));
+ }
+
+ // If we don't have a ::marker pseudo-element, but need it, then
+ // reconstruct the frame. (The opposite situation implies 'display'
+ // changes so doesn't need to be handled explicitly here.)
+ if (wasRestyled && styleFrame->StyleDisplay()->IsListItem() &&
+ styleFrame->IsBlockFrameOrSubclass() &&
+ !nsLayoutUtils::GetMarkerPseudo(aElement)) {
+ RefPtr<ComputedStyle> pseudoStyle =
+ aRestyleState.StyleSet().ProbePseudoElementStyle(
+ *aElement, PseudoStyleType::marker, nullptr,
+ upToDateStyleIfRestyled);
+ if (pseudoStyle) {
+ changeHint |= nsChangeHint_ReconstructFrame;
+ }
+ }
+ }
+
+ // Although we shouldn't generate non-ReconstructFrame hints for elements with
+ // no frames, we can still get them here if they were explicitly posted by
+ // PostRestyleEvent, such as a RepaintFrame hint when a :link changes to be
+ // :visited. Skip processing these hints if there is no frame.
+ if ((styleFrame || (changeHint & nsChangeHint_ReconstructFrame)) &&
+ changeHint) {
+ aRestyleState.ChangeList().AppendChange(styleFrame, aElement, changeHint);
+ }
+
+ // If our change hint is reconstruct, we delegate to the frame constructor,
+ // which consumes the new style and expects the old style to be on the frame.
+ //
+ // XXXbholley: We should teach the frame constructor how to clear the dirty
+ // descendants bit to avoid the traversal here.
+ if (changeHint & nsChangeHint_ReconstructFrame) {
+ if (wasRestyled &&
+ StaticPrefs::layout_css_scroll_anchoring_suppressions_enabled()) {
+ const bool wasAbsPos =
+ styleFrame &&
+ styleFrame->StyleDisplay()->IsAbsolutelyPositionedStyle();
+ auto* newDisp = upToDateStyleIfRestyled->StyleDisplay();
+ // https://drafts.csswg.org/css-scroll-anchoring/#suppression-triggers
+ //
+ // We need to do the position check here rather than in
+ // DidSetComputedStyle because changing position reframes.
+ //
+ // We suppress adjustments whenever we change from being display: none to
+ // be an abspos.
+ //
+ // Similarly, for other changes from abspos to non-abspos styles.
+ //
+ // TODO(emilio): I _think_ chrome won't suppress adjustments whenever
+ // `display` changes. But that causes some infinite loops in cases like
+ // bug 1568778.
+ if (wasAbsPos != newDisp->IsAbsolutelyPositionedStyle()) {
+ aRestyleState.AddPendingScrollAnchorSuppression(aElement);
+ }
+ }
+ ClearRestyleStateFromSubtree(aElement);
+ return true;
+ }
+
+ // TODO(emilio): We could avoid some refcount traffic here, specially in the
+ // ComputedStyle case, which uses atomic refcounting.
+ //
+ // Hold the ComputedStyle alive, because it could become a dangling pointer
+ // during the replacement. In practice it's not a huge deal, but better not
+ // playing with dangling pointers if not needed.
+ //
+ // NOTE(emilio): We could keep around the old computed style for display:
+ // contents elements too, but we don't really need it right now.
+ RefPtr<ComputedStyle> oldOrDisplayContentsStyle =
+ styleFrame ? styleFrame->Style() : nullptr;
+
+ MOZ_ASSERT(!(styleFrame && Servo_Element_IsDisplayContents(aElement)),
+ "display: contents node has a frame, yet we didn't reframe it"
+ " above?");
+ const bool isDisplayContents = !styleFrame && aElement->HasServoData() &&
+ Servo_Element_IsDisplayContents(aElement);
+ if (isDisplayContents) {
+ oldOrDisplayContentsStyle = ServoStyleSet::ResolveServoStyle(*aElement);
+ }
+
+ Maybe<ServoRestyleState> thisFrameRestyleState;
+ if (styleFrame) {
+ auto type = isOutOfFlow || isColumnSpan ? ServoRestyleState::Type::OutOfFlow
+ : ServoRestyleState::Type::InFlow;
+
+ thisFrameRestyleState.emplace(*styleFrame, aRestyleState, changeHint, type);
+ }
+
+ // We can't really assume as used changes from display: contents elements (or
+ // other elements without frames).
+ ServoRestyleState& childrenRestyleState =
+ thisFrameRestyleState ? *thisFrameRestyleState : aRestyleState;
+
+ ComputedStyle* upToDateStyle =
+ wasRestyled ? upToDateStyleIfRestyled : oldOrDisplayContentsStyle;
+
+ ServoPostTraversalFlags childrenFlags =
+ wasRestyled ? ServoPostTraversalFlags::ParentWasRestyled
+ : ServoPostTraversalFlags::Empty;
+
+ if (wasRestyled && oldOrDisplayContentsStyle) {
+ MOZ_ASSERT(styleFrame || isDisplayContents);
+
+ // We want to walk all the continuations here, even the ones with different
+ // styles. In practice, the only reason we get continuations with different
+ // styles here is ::first-line (::first-letter never affects element
+ // styles). But in that case, newStyle is the right context for the
+ // _later_ continuations anyway (the ones not affected by ::first-line), not
+ // the earlier ones, so there is no point stopping right at the point when
+ // we'd actually be setting the right ComputedStyle.
+ //
+ // This does mean that we may be setting the wrong ComputedStyle on our
+ // initial continuations; ::first-line fixes that up after the fact.
+ for (nsIFrame* f = styleFrame; f; f = f->GetNextContinuation()) {
+ MOZ_ASSERT_IF(f != styleFrame, !f->GetAdditionalComputedStyle(0));
+ f->SetComputedStyle(upToDateStyle);
+ }
+
+ if (styleFrame) {
+ UpdateAdditionalComputedStyles(styleFrame, aRestyleState);
+ }
+
+ if (!aElement->GetParent()) {
+ // This is the root. Update styles on the viewport as needed.
+ ViewportFrame* viewport =
+ do_QueryFrame(mPresContext->PresShell()->GetRootFrame());
+ if (viewport) {
+ // NB: The root restyle state, not the one for our children!
+ viewport->UpdateStyle(aRestyleState);
+ }
+ }
+
+ // Some changes to animations don't affect the computed style and yet still
+ // require the layer to be updated. For example, pausing an animation via
+ // the Web Animations API won't affect an element's style but still
+ // requires to update the animation on the layer.
+ //
+ // We can sometimes reach this when the animated style is being removed.
+ // Since AddLayerChangesForAnimation checks if |styleFrame| has a transform
+ // style or not, we need to call it *after* setting |newStyle| to
+ // |styleFrame| to ensure the animated transform has been removed first.
+ AddLayerChangesForAnimation(styleFrame, primaryFrame, aElement, changeHint,
+ aRestyleState.ChangeList());
+
+ childrenFlags |= SendA11yNotifications(mPresContext, aElement,
+ *oldOrDisplayContentsStyle,
+ *upToDateStyle, aFlags);
+ }
+
+ const bool traverseElementChildren =
+ aElement->HasAnyOfFlags(Element::kAllServoDescendantBits);
+ const bool traverseTextChildren =
+ wasRestyled || aElement->HasFlag(NODE_DESCENDANTS_NEED_FRAMES);
+ bool recreatedAnyContext = wasRestyled;
+ if (traverseElementChildren || traverseTextChildren) {
+ StyleChildrenIterator it(aElement);
+ TextPostTraversalState textState(*aElement, upToDateStyle,
+ isDisplayContents && wasRestyled,
+ childrenRestyleState);
+ for (nsIContent* n = it.GetNextChild(); n; n = it.GetNextChild()) {
+ if (traverseElementChildren && n->IsElement()) {
+ recreatedAnyContext |= ProcessPostTraversal(
+ n->AsElement(), childrenRestyleState, childrenFlags);
+ } else if (traverseTextChildren && n->IsText()) {
+ recreatedAnyContext |= ProcessPostTraversalForText(
+ n, textState, childrenRestyleState, childrenFlags);
+ }
+ }
+ }
+
+ // We want to update frame pseudo-element styles after we've traversed our
+ // kids, because some of those updates (::first-line/::first-letter) need to
+ // modify the styles of the kids, and the child traversal above would just
+ // clobber those modifications.
+ if (styleFrame) {
+ if (wasRestyled) {
+ // Make sure to update anon boxes and pseudo bits after updating text,
+ // otherwise ProcessPostTraversalForText could clobber first-letter
+ // styles, for example.
+ styleFrame->UpdateStyleOfOwnedAnonBoxes(childrenRestyleState);
+ }
+ // Process anon box wrapper frames before ::first-line bits, but _after_
+ // owned anon boxes, since the children wrapper anon boxes could be
+ // inheriting from our own owned anon boxes.
+ childrenRestyleState.ProcessWrapperRestyles(styleFrame);
+ if (wasRestyled) {
+ UpdateFramePseudoElementStyles(styleFrame, childrenRestyleState);
+ } else if (traverseElementChildren &&
+ styleFrame->IsBlockFrameOrSubclass()) {
+ // Even if we were not restyled, if we're a block with a first-line and
+ // one of our descendant elements which is on the first line was restyled,
+ // we need to update the styles of things on the first line, because
+ // they're wrong now.
+ //
+ // FIXME(bz) Could we do better here? For example, could we keep track of
+ // frames that are "block with a ::first-line so we could avoid
+ // IsFrameOfType() and digging about for the first-line frame if not?
+ // Could we keep track of whether the element children we actually restyle
+ // are affected by first-line? Something else? Bug 1385443 tracks making
+ // this better.
+ nsIFrame* firstLineFrame =
+ static_cast<nsBlockFrame*>(styleFrame)->GetFirstLineFrame();
+ if (firstLineFrame) {
+ for (nsIFrame* kid : firstLineFrame->PrincipalChildList()) {
+ ReparentComputedStyleForFirstLine(kid);
+ }
+ }
+ }
+ }
+
+ aElement->UnsetFlags(Element::kAllServoDescendantBits);
+ return recreatedAnyContext;
+}
+
+bool RestyleManager::ProcessPostTraversalForText(
+ nsIContent* aTextNode, TextPostTraversalState& aPostTraversalState,
+ ServoRestyleState& aRestyleState, ServoPostTraversalFlags aFlags) {
+ // Handle lazy frame construction.
+ if (aTextNode->HasFlag(NODE_NEEDS_FRAME)) {
+ aPostTraversalState.ChangeList().AppendChange(
+ nullptr, aTextNode, nsChangeHint_ReconstructFrame);
+ return true;
+ }
+
+ // Handle restyle.
+ nsIFrame* primaryFrame = aTextNode->GetPrimaryFrame();
+ if (!primaryFrame) {
+ return false;
+ }
+
+ // If the parent wasn't restyled, the styles of our anon box parents won't
+ // change either.
+ if ((aFlags & ServoPostTraversalFlags::ParentWasRestyled) &&
+ primaryFrame->ParentIsWrapperAnonBox()) {
+ aRestyleState.AddPendingWrapperRestyle(
+ ServoRestyleState::TableAwareParentFor(primaryFrame));
+ }
+
+ ComputedStyle& newStyle = aPostTraversalState.ComputeStyle(aTextNode);
+ aPostTraversalState.ComputeHintIfNeeded(aTextNode, primaryFrame, newStyle);
+
+ // We want to walk all the continuations here, even the ones with different
+ // styles. In practice, the only reasons we get continuations with different
+ // styles are ::first-line and ::first-letter. But in those cases,
+ // newStyle is the right context for the _later_ continuations anyway (the
+ // ones not affected by ::first-line/::first-letter), not the earlier ones,
+ // so there is no point stopping right at the point when we'd actually be
+ // setting the right ComputedStyle.
+ //
+ // This does mean that we may be setting the wrong ComputedStyle on our
+ // initial continuations; ::first-line/::first-letter fix that up after the
+ // fact.
+ for (nsIFrame* f = primaryFrame; f; f = f->GetNextContinuation()) {
+ f->SetComputedStyle(&newStyle);
+ }
+
+ return true;
+}
+
+void RestyleManager::ClearSnapshots() {
+ for (auto iter = mSnapshots.Iter(); !iter.Done(); iter.Next()) {
+ iter.Key()->UnsetFlags(ELEMENT_HAS_SNAPSHOT | ELEMENT_HANDLED_SNAPSHOT);
+ iter.Remove();
+ }
+}
+
+ServoElementSnapshot& RestyleManager::SnapshotFor(Element& aElement) {
+ MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
+
+ // NOTE(emilio): We can handle snapshots from a one-off restyle of those that
+ // we do to restyle stuff for reconstruction, for example.
+ //
+ // It seems to be the case that we always flush in between that happens and
+ // the next attribute change, so we can assert that we haven't handled the
+ // snapshot here yet. If this assertion didn't hold, we'd need to unset that
+ // flag from here too.
+ //
+ // Can't wait to make ProcessPendingRestyles the only entry-point for styling,
+ // so this becomes much easier to reason about. Today is not that day though.
+ MOZ_ASSERT(!aElement.HasFlag(ELEMENT_HANDLED_SNAPSHOT));
+
+ ServoElementSnapshot* snapshot =
+ mSnapshots.GetOrInsertNew(&aElement, aElement);
+ aElement.SetFlags(ELEMENT_HAS_SNAPSHOT);
+
+ // Now that we have a snapshot, make sure a restyle is triggered.
+ aElement.NoteDirtyForServo();
+ return *snapshot;
+}
+
+void RestyleManager::DoProcessPendingRestyles(ServoTraversalFlags aFlags) {
+ nsPresContext* presContext = PresContext();
+ PresShell* presShell = presContext->PresShell();
+
+ MOZ_ASSERT(presContext->Document(), "No document? Pshaw!");
+ // FIXME(emilio): In the "flush animations" case, ideally, we should only
+ // recascade animation styles running on the compositor, so we shouldn't care
+ // about other styles, or new rules that apply to the page...
+ //
+ // However, that's not true as of right now, see bug 1388031 and bug 1388692.
+ MOZ_ASSERT((aFlags & ServoTraversalFlags::FlushThrottledAnimations) ||
+ !presContext->HasPendingMediaQueryUpdates(),
+ "Someone forgot to update media queries?");
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(), "Missing a script blocker!");
+ MOZ_RELEASE_ASSERT(!mInStyleRefresh, "Reentrant call?");
+
+ if (MOZ_UNLIKELY(!presShell->DidInitialize())) {
+ // PresShell::FlushPendingNotifications doesn't early-return in the case
+ // where the PresShell hasn't yet been initialized (and therefore we haven't
+ // yet done the initial style traversal of the DOM tree). We should arguably
+ // fix up the callers and assert against this case, but we just detect and
+ // handle it for now.
+ return;
+ }
+
+ // It'd be bad!
+ PresShell::AutoAssertNoFlush noReentrantFlush(*presShell);
+
+ // Create a AnimationsWithDestroyedFrame during restyling process to
+ // stop animations and transitions on elements that have no frame at the end
+ // of the restyling process.
+ AnimationsWithDestroyedFrame animationsWithDestroyedFrame(this);
+
+ ServoStyleSet* styleSet = StyleSet();
+ Document* doc = presContext->Document();
+
+ // Ensure the refresh driver is active during traversal to avoid mutating
+ // mActiveTimer and mMostRecentRefresh time.
+ presContext->RefreshDriver()->MostRecentRefresh();
+
+ if (!doc->GetServoRestyleRoot()) {
+ // This might post new restyles, so need to do it here. Don't do it if we're
+ // already going to restyle tho, so that we don't potentially reflow with
+ // dirty styling.
+ presContext->UpdateContainerQueryStyles();
+ presContext->FinishedContainerQueryUpdate();
+ }
+
+ // Perform the Servo traversal, and the post-traversal if required. We do this
+ // in a loop because certain rare paths in the frame constructor can trigger
+ // additional style invalidations.
+ //
+ // FIXME(emilio): Confirm whether that's still true now that XBL is gone.
+ mInStyleRefresh = true;
+ if (mHaveNonAnimationRestyles) {
+ ++mAnimationGeneration;
+ }
+
+ if (mRestyleForCSSRuleChanges) {
+ aFlags |= ServoTraversalFlags::ForCSSRuleChanges;
+ }
+
+ while (styleSet->StyleDocument(aFlags)) {
+ ClearSnapshots();
+
+ // Select scroll anchors for frames that have been scrolled. Do this
+ // before processing restyled frames so that anchor nodes are correctly
+ // marked when directly moving frames with RecomputePosition.
+ presContext->PresShell()->FlushPendingScrollAnchorSelections();
+
+ nsStyleChangeList currentChanges;
+ bool anyStyleChanged = false;
+
+ // Recreate styles , and queue up change hints (which also handle lazy frame
+ // construction).
+ nsTArray<RefPtr<Element>> anchorsToSuppress;
+
+ {
+ DocumentStyleRootIterator iter(doc->GetServoRestyleRoot());
+ while (Element* root = iter.GetNextStyleRoot()) {
+ nsTArray<nsIFrame*> wrappersToRestyle;
+ ServoRestyleState state(*styleSet, currentChanges, wrappersToRestyle,
+ anchorsToSuppress);
+ ServoPostTraversalFlags flags = ServoPostTraversalFlags::Empty;
+ anyStyleChanged |= ProcessPostTraversal(root, state, flags);
+ }
+
+ // We want to suppress adjustments the current (before-change) scroll
+ // anchor container now, and save a reference to the content node so that
+ // we can suppress them in the after-change scroll anchor .
+ for (Element* element : anchorsToSuppress) {
+ if (nsIFrame* frame = element->GetPrimaryFrame()) {
+ if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
+ container->SuppressAdjustments();
+ }
+ }
+ }
+ }
+
+ doc->ClearServoRestyleRoot();
+ ClearSnapshots();
+
+ // Process the change hints.
+ //
+ // Unfortunately, the frame constructor can generate new change hints while
+ // processing existing ones. We redirect those into a secondary queue and
+ // iterate until there's nothing left.
+ {
+ ReentrantChangeList newChanges;
+ mReentrantChanges = &newChanges;
+ while (!currentChanges.IsEmpty()) {
+ ProcessRestyledFrames(currentChanges);
+ MOZ_ASSERT(currentChanges.IsEmpty());
+ for (ReentrantChange& change : newChanges) {
+ if (!(change.mHint & nsChangeHint_ReconstructFrame) &&
+ !change.mContent->GetPrimaryFrame()) {
+ // SVG Elements post change hints without ensuring that the primary
+ // frame will be there after that (see bug 1366142).
+ //
+ // Just ignore those, since we can't really process them.
+ continue;
+ }
+ currentChanges.AppendChange(change.mContent->GetPrimaryFrame(),
+ change.mContent, change.mHint);
+ }
+ newChanges.Clear();
+ }
+ mReentrantChanges = nullptr;
+ }
+
+ // Suppress adjustments in the after-change scroll anchors if needed, now
+ // that we're done reframing everything.
+ for (Element* element : anchorsToSuppress) {
+ if (nsIFrame* frame = element->GetPrimaryFrame()) {
+ if (auto* container = ScrollAnchorContainer::FindFor(frame)) {
+ container->SuppressAdjustments();
+ }
+ }
+ }
+
+ if (anyStyleChanged) {
+ // Maybe no styles changed when:
+ //
+ // * Only explicit change hints were posted in the first place.
+ // * When an attribute or state change in the content happens not to need
+ // a restyle after all.
+ //
+ // In any case, we don't need to increment the restyle generation in that
+ // case.
+ IncrementRestyleGeneration();
+ }
+
+ mInStyleRefresh = false;
+ presContext->UpdateContainerQueryStyles();
+ mInStyleRefresh = true;
+ }
+
+ doc->ClearServoRestyleRoot();
+ presContext->FinishedContainerQueryUpdate();
+ presContext->UpdateHiddenByContentVisibilityForAnimationsIfNeeded();
+ ClearSnapshots();
+ styleSet->AssertTreeIsClean();
+
+ mHaveNonAnimationRestyles = false;
+ mRestyleForCSSRuleChanges = false;
+ mInStyleRefresh = false;
+
+ // Now that everything has settled, see if we have enough free rule nodes in
+ // the tree to warrant sweeping them.
+ styleSet->MaybeGCRuleTree();
+
+ // Note: We are in the scope of |animationsWithDestroyedFrame|, so
+ // |mAnimationsWithDestroyedFrame| is still valid.
+ MOZ_ASSERT(mAnimationsWithDestroyedFrame);
+ mAnimationsWithDestroyedFrame->StopAnimationsForElementsWithoutFrames();
+}
+
+#ifdef DEBUG
+static void VerifyFlatTree(const nsIContent& aContent) {
+ StyleChildrenIterator iter(&aContent);
+
+ for (auto* content = iter.GetNextChild(); content;
+ content = iter.GetNextChild()) {
+ MOZ_ASSERT(content->GetFlattenedTreeParentNodeForStyle() == &aContent);
+ VerifyFlatTree(*content);
+ }
+}
+#endif
+
+void RestyleManager::ProcessPendingRestyles() {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Styles", LAYOUT);
+#ifdef DEBUG
+ if (auto* root = mPresContext->Document()->GetRootElement()) {
+ VerifyFlatTree(*root);
+ }
+#endif
+
+ DoProcessPendingRestyles(ServoTraversalFlags::Empty);
+}
+
+void RestyleManager::ProcessAllPendingAttributeAndStateInvalidations() {
+ if (mSnapshots.IsEmpty()) {
+ return;
+ }
+ for (const auto& key : mSnapshots.Keys()) {
+ // Servo data for the element might have been dropped. (e.g. by removing
+ // from its document)
+ if (key->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
+ Servo_ProcessInvalidations(StyleSet()->RawData(), key, &mSnapshots);
+ }
+ }
+ ClearSnapshots();
+}
+
+void RestyleManager::UpdateOnlyAnimationStyles() {
+ bool doCSS = PresContext()->EffectCompositor()->HasPendingStyleUpdates();
+ if (!doCSS) {
+ return;
+ }
+
+ DoProcessPendingRestyles(ServoTraversalFlags::FlushThrottledAnimations);
+}
+
+void RestyleManager::ElementStateChanged(Element* aElement,
+ ElementState aChangedBits) {
+#ifdef EARLY_BETA_OR_EARLIER
+ if (MOZ_UNLIKELY(mInStyleRefresh)) {
+ MOZ_CRASH_UNSAFE_PRINTF(
+ "Element state change during style refresh (%" PRIu64 ")",
+ aChangedBits.GetInternalValue());
+ }
+#endif
+
+ const ElementState kVisitedAndUnvisited =
+ ElementState::VISITED | ElementState::UNVISITED;
+
+ // We'll restyle when the relevant visited query finishes, regardless of the
+ // style (see Link::VisitedQueryFinished). So there's no need to do anything
+ // as a result of this state change just yet.
+ //
+ // Note that this check checks for _both_ bits: This is only true when visited
+ // changes to unvisited or vice-versa, but not when we start or stop being a
+ // link itself.
+ if (aChangedBits.HasAllStates(kVisitedAndUnvisited)) {
+ aChangedBits &= ~kVisitedAndUnvisited;
+ if (aChangedBits.IsEmpty()) {
+ return;
+ }
+ }
+
+ if (auto changeHint = ChangeForContentStateChange(*aElement, aChangedBits)) {
+ Servo_NoteExplicitHints(aElement, RestyleHint{0}, changeHint);
+ }
+
+ // Don't bother taking a snapshot if no rules depend on these state bits.
+ //
+ // We always take a snapshot for the LTR/RTL event states, since Servo doesn't
+ // track those bits in the same way, and we know that :dir() rules are always
+ // present in UA style sheets.
+ if (!aChangedBits.HasAtLeastOneOfStates(ElementState::DIR_STATES) &&
+ !StyleSet()->HasStateDependency(*aElement, aChangedBits)) {
+ return;
+ }
+
+ // Assuming we need to invalidate cached style in getComputedStyle for
+ // undisplayed elements, since we don't know if it is needed.
+ IncrementUndisplayedRestyleGeneration();
+
+ if (!aElement->HasServoData() &&
+ !(aElement->GetSelectorFlags() &
+ NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
+ return;
+ }
+
+ ServoElementSnapshot& snapshot = SnapshotFor(*aElement);
+ ElementState previousState = aElement->StyleState() ^ aChangedBits;
+ snapshot.AddState(previousState);
+
+ ServoStyleSet& styleSet = *StyleSet();
+ MaybeRestyleForNthOfState(styleSet, aElement, aChangedBits);
+ MaybeRestyleForRelativeSelectorState(styleSet, aElement, aChangedBits);
+}
+
+void RestyleManager::MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet,
+ Element* aChild,
+ ElementState aChangedBits) {
+ const auto* parentNode = aChild->GetParentNode();
+ MOZ_ASSERT(parentNode);
+ const auto parentFlags = parentNode->GetSelectorFlags();
+ if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
+ return;
+ }
+
+ if (aStyleSet.HasNthOfStateDependency(*aChild, aChangedBits)) {
+ RestyleSiblingsForNthOf(aChild, parentFlags);
+ }
+}
+
+static inline bool AttributeInfluencesOtherPseudoClassState(
+ const Element& aElement, const nsAtom* aAttribute) {
+ // We must record some state for :-moz-browser-frame,
+ // :-moz-table-border-nonzero, and :-moz-select-list-box.
+ if (aAttribute == nsGkAtoms::mozbrowser) {
+ return aElement.IsAnyOfHTMLElements(nsGkAtoms::iframe, nsGkAtoms::frame);
+ }
+
+ if (aAttribute == nsGkAtoms::border) {
+ return aElement.IsHTMLElement(nsGkAtoms::table);
+ }
+
+ if (aAttribute == nsGkAtoms::multiple || aAttribute == nsGkAtoms::size) {
+ return aElement.IsHTMLElement(nsGkAtoms::select);
+ }
+
+ return false;
+}
+
+static inline bool NeedToRecordAttrChange(
+ const ServoStyleSet& aStyleSet, const Element& aElement,
+ int32_t aNameSpaceID, nsAtom* aAttribute,
+ bool* aInfluencesOtherPseudoClassState) {
+ *aInfluencesOtherPseudoClassState =
+ AttributeInfluencesOtherPseudoClassState(aElement, aAttribute);
+
+ // If the attribute influences one of the pseudo-classes that are backed by
+ // attributes, we just record it.
+ if (*aInfluencesOtherPseudoClassState) {
+ return true;
+ }
+
+ // We assume that id and class attributes are used in class/id selectors, and
+ // thus record them.
+ //
+ // TODO(emilio): We keep a filter of the ids in use somewhere in the StyleSet,
+ // presumably we could try to filter the old and new id, but it's not clear
+ // it's worth it.
+ if (aNameSpaceID == kNameSpaceID_None &&
+ (aAttribute == nsGkAtoms::id || aAttribute == nsGkAtoms::_class)) {
+ return true;
+ }
+
+ // We always record lang="", even though we force a subtree restyle when it
+ // changes, since it can change how its siblings match :lang(..) due to
+ // selectors like :lang(..) + div.
+ if (aAttribute == nsGkAtoms::lang) {
+ return true;
+ }
+
+ // Otherwise, just record the attribute change if a selector in the page may
+ // reference it from an attribute selector.
+ return aStyleSet.MightHaveAttributeDependency(aElement, aAttribute);
+}
+
+void RestyleManager::AttributeWillChange(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType) {
+ TakeSnapshotForAttributeChange(*aElement, aNameSpaceID, aAttribute);
+}
+
+void RestyleManager::ClassAttributeWillBeChangedBySMIL(Element* aElement) {
+ TakeSnapshotForAttributeChange(*aElement, kNameSpaceID_None,
+ nsGkAtoms::_class);
+}
+
+void RestyleManager::TakeSnapshotForAttributeChange(Element& aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute) {
+ MOZ_DIAGNOSTIC_ASSERT(!mInStyleRefresh);
+
+ bool influencesOtherPseudoClassState;
+ if (!NeedToRecordAttrChange(*StyleSet(), aElement, aNameSpaceID, aAttribute,
+ &influencesOtherPseudoClassState)) {
+ return;
+ }
+
+ // We cannot tell if the attribute change will affect the styles of
+ // undisplayed elements, because we don't actually restyle those elements
+ // during the restyle traversal. So just assume that the attribute change can
+ // cause the style to change.
+ IncrementUndisplayedRestyleGeneration();
+
+ // Relative selector invalidation travels ancestor and earlier sibling
+ // direction, so it's very possible that it invalidates a styled element.
+ if (!aElement.HasServoData() &&
+ !(aElement.GetSelectorFlags() &
+ NodeSelectorFlags::RelativeSelectorSearchDirectionAncestorSibling)) {
+ return;
+ }
+
+ // Some other random attribute changes may also affect the transitions,
+ // so we also set this true here.
+ mHaveNonAnimationRestyles = true;
+
+ ServoElementSnapshot& snapshot = SnapshotFor(aElement);
+ snapshot.AddAttrs(aElement, aNameSpaceID, aAttribute);
+
+ if (influencesOtherPseudoClassState) {
+ snapshot.AddOtherPseudoClassState(aElement);
+ }
+}
+
+// For some attribute changes we must restyle the whole subtree:
+//
+// * lang="" and xml:lang="" can affect all descendants due to :lang()
+// * exportparts can affect all descendant parts. We could certainly integrate
+// it better in the invalidation machinery if it was necessary.
+static inline bool AttributeChangeRequiresSubtreeRestyle(
+ const Element& aElement, nsAtom* aAttr) {
+ if (aAttr == nsGkAtoms::exportparts) {
+ // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for
+ // exportparts attribute changes?
+ return !!aElement.GetShadowRoot();
+ }
+ return aAttr == nsGkAtoms::lang;
+}
+
+void RestyleManager::AttributeChanged(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ MOZ_ASSERT(!mInStyleRefresh);
+
+ auto changeHint = nsChangeHint(0);
+ auto restyleHint = RestyleHint{0};
+
+ changeHint |= aElement->GetAttributeChangeHint(aAttribute, aModType);
+
+ MaybeRestyleForNthOfAttribute(aElement, aAttribute, aOldValue);
+ MaybeRestyleForRelativeSelectorAttribute(aElement, aAttribute, aOldValue);
+
+ if (aAttribute == nsGkAtoms::style) {
+ restyleHint |= RestyleHint::RESTYLE_STYLE_ATTRIBUTE;
+ } else if (AttributeChangeRequiresSubtreeRestyle(*aElement, aAttribute)) {
+ restyleHint |= RestyleHint::RestyleSubtree();
+ } else if (aElement->IsInShadowTree() && aAttribute == nsGkAtoms::part) {
+ // TODO(emilio, bug 1598094): Maybe finer-grained invalidation for part
+ // attribute changes?
+ restyleHint |= RestyleHint::RESTYLE_SELF | RestyleHint::RESTYLE_PSEUDOS;
+ }
+
+ if (nsIFrame* primaryFrame = aElement->GetPrimaryFrame()) {
+ // See if we have appearance information for a theme.
+ StyleAppearance appearance =
+ primaryFrame->StyleDisplay()->EffectiveAppearance();
+ if (appearance != StyleAppearance::None) {
+ nsITheme* theme = PresContext()->Theme();
+ if (theme->ThemeSupportsWidget(PresContext(), primaryFrame, appearance)) {
+ bool repaint = false;
+ theme->WidgetStateChanged(primaryFrame, appearance, aAttribute,
+ &repaint, aOldValue);
+ if (repaint) {
+ changeHint |= nsChangeHint_RepaintFrame;
+ }
+ }
+ }
+
+ primaryFrame->AttributeChanged(aNameSpaceID, aAttribute, aModType);
+ }
+
+ if (restyleHint || changeHint) {
+ Servo_NoteExplicitHints(aElement, restyleHint, changeHint);
+ }
+
+ if (restyleHint) {
+ // Assuming we need to invalidate cached style in getComputedStyle for
+ // undisplayed elements, since we don't know if it is needed.
+ IncrementUndisplayedRestyleGeneration();
+
+ // If we change attributes, we have to mark this to be true, so we will
+ // increase the animation generation for the new created transition if any.
+ mHaveNonAnimationRestyles = true;
+ }
+}
+
+void RestyleManager::RestyleSiblingsForNthOf(Element* aChild,
+ NodeSelectorFlags aParentFlags) {
+ StyleSet()->RestyleSiblingsForNthOf(*aChild,
+ static_cast<uint32_t>(aParentFlags));
+}
+
+void RestyleManager::MaybeRestyleForNthOfAttribute(
+ Element* aChild, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
+ const auto* parentNode = aChild->GetParentNode();
+ MOZ_ASSERT(parentNode);
+ const auto parentFlags = parentNode->GetSelectorFlags();
+ if (!(parentFlags & NodeSelectorFlags::HasSlowSelectorNthOf)) {
+ return;
+ }
+ if (!aChild->HasServoData()) {
+ return;
+ }
+
+ bool mightHaveNthOfDependency;
+ auto& styleSet = *StyleSet();
+ if (aAttribute == nsGkAtoms::id) {
+ auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
+ ? aOldValue->GetAtomValue()
+ : nullptr;
+ mightHaveNthOfDependency =
+ styleSet.MightHaveNthOfIDDependency(*aChild, oldAtom, aChild->GetID());
+ } else if (aAttribute == nsGkAtoms::_class) {
+ mightHaveNthOfDependency = styleSet.MightHaveNthOfClassDependency(*aChild);
+ } else {
+ mightHaveNthOfDependency =
+ styleSet.MightHaveNthOfAttributeDependency(*aChild, aAttribute);
+ }
+
+ if (mightHaveNthOfDependency) {
+ RestyleSiblingsForNthOf(aChild, parentFlags);
+ }
+}
+
+void RestyleManager::MaybeRestyleForRelativeSelectorAttribute(
+ Element* aElement, nsAtom* aAttribute, const nsAttrValue* aOldValue) {
+ if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
+ return;
+ }
+ auto& styleSet = *StyleSet();
+ if (aAttribute == nsGkAtoms::id) {
+ auto* const oldAtom = aOldValue->Type() == nsAttrValue::eAtom
+ ? aOldValue->GetAtomValue()
+ : nullptr;
+ styleSet.MaybeInvalidateRelativeSelectorIDDependency(
+ *aElement, oldAtom, aElement->GetID(), Snapshots());
+ } else if (aAttribute == nsGkAtoms::_class) {
+ styleSet.MaybeInvalidateRelativeSelectorClassDependency(*aElement,
+ Snapshots());
+ } else {
+ styleSet.MaybeInvalidateRelativeSelectorAttributeDependency(
+ *aElement, aAttribute, Snapshots());
+ }
+}
+
+void RestyleManager::MaybeRestyleForRelativeSelectorState(
+ ServoStyleSet& aStyleSet, Element* aElement, ElementState aChangedBits) {
+ if (!aElement->HasFlag(ELEMENT_HAS_SNAPSHOT)) {
+ return;
+ }
+ aStyleSet.MaybeInvalidateRelativeSelectorStateDependency(
+ *aElement, aChangedBits, Snapshots());
+}
+
+void RestyleManager::ReparentComputedStyleForFirstLine(nsIFrame* aFrame) {
+ // This is only called when moving frames in or out of the first-line
+ // pseudo-element (or one of its descendants). We can't say much about
+ // aFrame's ancestors, unfortunately (e.g. during a dynamic insert into
+ // something inside an inline-block on the first line the ancestors could be
+ // totally arbitrary), but we will definitely find a line frame on the
+ // ancestor chain. Note that the lineframe may not actually be the one that
+ // corresponds to ::first-line; when we're moving _out_ of the ::first-line it
+ // will be one of the continuations instead.
+#ifdef DEBUG
+ {
+ nsIFrame* f = aFrame->GetParent();
+ while (f && !f->IsLineFrame()) {
+ f = f->GetParent();
+ }
+ MOZ_ASSERT(f, "Must have found a first-line frame");
+ }
+#endif
+
+ DoReparentComputedStyleForFirstLine(aFrame, *StyleSet());
+}
+
+static bool IsFrameAboutToGoAway(nsIFrame* aFrame) {
+ auto* element = Element::FromNode(aFrame->GetContent());
+ if (!element) {
+ return false;
+ }
+ return !element->HasServoData();
+}
+
+void RestyleManager::DoReparentComputedStyleForFirstLine(
+ nsIFrame* aFrame, ServoStyleSet& aStyleSet) {
+ if (aFrame->IsBackdropFrame()) {
+ // Style context of backdrop frame has no parent style, and thus we do not
+ // need to reparent it.
+ return;
+ }
+
+ if (IsFrameAboutToGoAway(aFrame)) {
+ // We're entering a display: none subtree, which we know it's going to get
+ // rebuilt. Don't bother reparenting.
+ return;
+ }
+
+ if (aFrame->IsPlaceholderFrame()) {
+ // Also reparent the out-of-flow and all its continuations. We're doing
+ // this to match Gecko for now, but it's not clear that this behavior is
+ // correct per spec. It's certainly pretty odd for out-of-flows whose
+ // containing block is not within the first line.
+ //
+ // Right now we're somewhat inconsistent in this testcase:
+ //
+ // <style>
+ // div { color: orange; clear: left; }
+ // div::first-line { color: blue; }
+ // </style>
+ // <div>
+ // <span style="float: left">What color is this text?</span>
+ // </div>
+ // <div>
+ // <span><span style="float: left">What color is this text?</span></span>
+ // </div>
+ //
+ // We make the first float orange and the second float blue. On the other
+ // hand, if the float were within an inline-block that was on the first
+ // line, arguably it _should_ inherit from the ::first-line...
+ nsIFrame* outOfFlow =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ MOZ_ASSERT(outOfFlow, "no out-of-flow frame");
+ for (; outOfFlow; outOfFlow = outOfFlow->GetNextContinuation()) {
+ DoReparentComputedStyleForFirstLine(outOfFlow, aStyleSet);
+ }
+ }
+
+ // FIXME(emilio): This is the only caller of GetParentComputedStyle, let's try
+ // to remove it?
+ nsIFrame* providerFrame;
+ ComputedStyle* newParentStyle =
+ aFrame->GetParentComputedStyle(&providerFrame);
+ // If our provider is our child, we want to reparent it first, because we
+ // inherit style from it.
+ bool isChild = providerFrame && providerFrame->GetParent() == aFrame;
+ nsIFrame* providerChild = nullptr;
+ if (isChild) {
+ DoReparentComputedStyleForFirstLine(providerFrame, aStyleSet);
+ // Get the style again after ReparentComputedStyle() which might have
+ // changed it.
+ newParentStyle = providerFrame->Style();
+ providerChild = providerFrame;
+ MOZ_ASSERT(!providerFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW),
+ "Out of flow provider?");
+ }
+
+ if (!newParentStyle) {
+ // No need to do anything here for this frame, but we should still reparent
+ // its descendants, because those may have styles that inherit from the
+ // parent of this frame (e.g. non-anonymous columns in an anonymous
+ // colgroup).
+ MOZ_ASSERT(aFrame->Style()->IsNonInheritingAnonBox(),
+ "Why did this frame not end up with a parent context?");
+ ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
+ return;
+ }
+
+ bool isElement = aFrame->GetContent()->IsElement();
+
+ // We probably don't want to initiate transitions from ReparentComputedStyle,
+ // since we call it during frame construction rather than in response to
+ // dynamic changes.
+ // Also see the comment at the start of
+ // nsTransitionManager::ConsiderInitiatingTransition.
+ //
+ // We don't try to do the fancy copying from previous continuations that
+ // GeckoRestyleManager does here, because that relies on knowing the parents
+ // of ComputedStyles, and we don't know those.
+ ComputedStyle* oldStyle = aFrame->Style();
+ Element* ourElement = isElement ? aFrame->GetContent()->AsElement() : nullptr;
+ ComputedStyle* newParent = newParentStyle;
+
+ if (!providerFrame) {
+ // No providerFrame means we inherited from a display:contents thing. Our
+ // layout parent style is the style of our nearest ancestor frame. But we
+ // have to be careful to do that with our placeholder, not with us, if we're
+ // out of flow.
+ if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ aFrame->FirstContinuation()
+ ->GetPlaceholderFrame()
+ ->GetLayoutParentStyleForOutOfFlow(&providerFrame);
+ } else {
+ providerFrame = nsIFrame::CorrectStyleParentFrame(
+ aFrame->GetParent(), oldStyle->GetPseudoType());
+ }
+ }
+ ComputedStyle* layoutParent = providerFrame->Style();
+
+ RefPtr<ComputedStyle> newStyle = aStyleSet.ReparentComputedStyle(
+ oldStyle, newParent, layoutParent, ourElement);
+ aFrame->SetComputedStyle(newStyle);
+
+ // This logic somewhat mirrors the logic in
+ // RestyleManager::ProcessPostTraversal.
+ if (isElement) {
+ // We can't use UpdateAdditionalComputedStyles as-is because it needs a
+ // ServoRestyleState and maintaining one of those during a _frametree_
+ // traversal is basically impossible.
+ int32_t index = 0;
+ while (auto* oldAdditionalStyle =
+ aFrame->GetAdditionalComputedStyle(index)) {
+ RefPtr<ComputedStyle> newAdditionalContext =
+ aStyleSet.ReparentComputedStyle(oldAdditionalStyle, newStyle,
+ newStyle, nullptr);
+ aFrame->SetAdditionalComputedStyle(index, newAdditionalContext);
+ ++index;
+ }
+ }
+
+ // Generally, owned anon boxes are our descendants. The only exceptions are
+ // tables (for the table wrapper) and inline frames (for the block part of the
+ // block-in-inline split). We're going to update our descendants when looping
+ // over kids, and we don't want to update the block part of a block-in-inline
+ // split if the inline is on the first line but the block is not (and if the
+ // block is, it's the child of something else on the first line and will get
+ // updated as a child). And given how this method ends up getting called, if
+ // we reach here for a table frame, we are already in the middle of
+ // reparenting the table wrapper frame. So no need to
+ // UpdateStyleOfOwnedAnonBoxes() here.
+
+ ReparentFrameDescendants(aFrame, providerChild, aStyleSet);
+
+ // We do not need to do the equivalent of UpdateFramePseudoElementStyles,
+ // because those are handled by our descendant walk.
+}
+
+void RestyleManager::ReparentFrameDescendants(nsIFrame* aFrame,
+ nsIFrame* aProviderChild,
+ ServoStyleSet& aStyleSet) {
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ // only do frames that are in flow
+ if (!child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ child != aProviderChild) {
+ DoReparentComputedStyleForFirstLine(child, aStyleSet);
+ }
+ }
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/base/RestyleManager.h b/layout/base/RestyleManager.h
new file mode 100644
index 0000000000..62fef15a16
--- /dev/null
+++ b/layout/base/RestyleManager.h
@@ -0,0 +1,606 @@
+/* -*- 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_RestyleManager_h
+#define mozilla_RestyleManager_h
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/OverflowChangedTracker.h"
+#include "mozilla/ServoElementSnapshot.h"
+#include "mozilla/ServoElementSnapshotTable.h"
+#include "nsChangeHint.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h" // XXX Shouldn't be included by header though
+#include "nsStringFwd.h"
+#include "nsTHashSet.h"
+
+class nsAttrValue;
+class nsAtom;
+class nsIFrame;
+class nsStyleChangeList;
+class nsStyleChangeList;
+
+namespace mozilla {
+
+class ServoStyleSet;
+
+namespace dom {
+class Element;
+}
+
+/**
+ * A stack class used to pass some common restyle state in a slightly more
+ * comfortable way than a bunch of individual arguments, and that also checks
+ * that the change hint used for optimization is correctly used in debug mode.
+ */
+class ServoRestyleState {
+ public:
+ ServoRestyleState(
+ ServoStyleSet& aStyleSet, nsStyleChangeList& aChangeList,
+ nsTArray<nsIFrame*>& aPendingWrapperRestyles,
+ nsTArray<RefPtr<dom::Element>>& aPendingScrollAnchorSuppressions)
+ : mStyleSet(aStyleSet),
+ mChangeList(aChangeList),
+ mPendingWrapperRestyles(aPendingWrapperRestyles),
+ mPendingScrollAnchorSuppressions(aPendingScrollAnchorSuppressions),
+ mPendingWrapperRestyleOffset(aPendingWrapperRestyles.Length()),
+ mChangesHandled(nsChangeHint(0))
+#ifdef DEBUG
+ // If !mOwner, then we wouldn't have processed our wrapper restyles,
+ // because we only process those when handling an element with a frame.
+ // But that's OK, because if we started our traversal at an element with
+ // no frame (e.g. it's display:contents), that means the wrapper frames
+ // in our list actually inherit from one of its ancestors, not from it,
+ // and hence not restyling them is OK.
+ ,
+ mAssertWrapperRestyleLength(false)
+#endif // DEBUG
+ {
+ }
+
+ // We shouldn't assume that changes handled from our parent are handled for
+ // our children too if we're out of flow since they aren't necessarily
+ // parented in DOM order, and thus a change handled by a DOM ancestor doesn't
+ // necessarily mean that it's handled for an ancestor frame.
+ enum class Type {
+ InFlow,
+ OutOfFlow,
+ };
+
+ ServoRestyleState(const nsIFrame& aOwner, ServoRestyleState& aParentState,
+ nsChangeHint aHintForThisFrame, Type aType,
+ bool aAssertWrapperRestyleLength = true)
+ : mStyleSet(aParentState.mStyleSet),
+ mChangeList(aParentState.mChangeList),
+ mPendingWrapperRestyles(aParentState.mPendingWrapperRestyles),
+ mPendingScrollAnchorSuppressions(
+ aParentState.mPendingScrollAnchorSuppressions),
+ mPendingWrapperRestyleOffset(
+ aParentState.mPendingWrapperRestyles.Length()),
+ mChangesHandled(aType == Type::InFlow
+ ? aParentState.mChangesHandled | aHintForThisFrame
+ : aHintForThisFrame)
+#ifdef DEBUG
+ ,
+ mOwner(&aOwner),
+ mAssertWrapperRestyleLength(aAssertWrapperRestyleLength)
+#endif
+ {
+ if (aType == Type::InFlow) {
+ AssertOwner(aParentState);
+ }
+ }
+
+ ~ServoRestyleState() {
+ MOZ_ASSERT(
+ !mAssertWrapperRestyleLength ||
+ mPendingWrapperRestyles.Length() == mPendingWrapperRestyleOffset,
+ "Someone forgot to call ProcessWrapperRestyles!");
+ }
+
+ nsStyleChangeList& ChangeList() { return mChangeList; }
+ ServoStyleSet& StyleSet() { return mStyleSet; }
+
+#ifdef DEBUG
+ void AssertOwner(const ServoRestyleState& aParentState) const;
+ nsChangeHint ChangesHandledFor(const nsIFrame*) const;
+#else
+ void AssertOwner(const ServoRestyleState&) const {}
+ nsChangeHint ChangesHandledFor(const nsIFrame*) const {
+ return mChangesHandled;
+ }
+#endif
+
+ // Add a pending wrapper restyle. We don't have to do anything if the thing
+ // being added is already last in the list, but otherwise we do want to add
+ // it, in order for ProcessWrapperRestyles to work correctly.
+ void AddPendingWrapperRestyle(nsIFrame* aWrapperFrame);
+
+ // Process wrapper restyles for this restyle state. This should be done
+ // before it comes off the stack.
+ void ProcessWrapperRestyles(nsIFrame* aParentFrame);
+
+ // Get the table-aware parent for the given child. This will walk through
+ // outer table and cellcontent frames.
+ static nsIFrame* TableAwareParentFor(const nsIFrame* aChild);
+
+ // When the value of the position property changes such as we stop or start
+ // being absolutely or fixed positioned, we need to suppress scroll anchoring
+ // adjustments to avoid breaking websites.
+ //
+ // We do need to process all this once we're done with all our reframes,
+ // to handle correctly the cases where we reconstruct an ancestor, like when
+ // you reframe an ib-split (see bug 1559627 for example).
+ //
+ // This doesn't handle nested reframes. We'd need to rework quite some code to
+ // do that, and so far it doesn't seem to be a problem in practice.
+ void AddPendingScrollAnchorSuppression(dom::Element* aElement) {
+ mPendingScrollAnchorSuppressions.AppendElement(aElement);
+ }
+
+ private:
+ // Process a wrapper restyle at the given index, and restyles for any
+ // wrappers nested in it. Returns the number of entries from
+ // mPendingWrapperRestyles that we processed. The return value is always at
+ // least 1.
+ size_t ProcessMaybeNestedWrapperRestyle(nsIFrame* aParent, size_t aIndex);
+
+ ServoStyleSet& mStyleSet;
+ nsStyleChangeList& mChangeList;
+
+ // A list of pending wrapper restyles. Anonymous box wrapper frames that need
+ // restyling are added to this list when their non-anonymous kids are
+ // restyled. This avoids us having to do linear searches along the frame tree
+ // for these anonymous boxes. The problem then becomes that we can have
+ // multiple kids all with the same anonymous parent, and we don't want to
+ // restyle it more than once. We use mPendingWrapperRestyles to track which
+ // anonymous wrapper boxes we've requested be restyled and which of them have
+ // already been restyled. We use a single array propagated through
+ // ServoRestyleStates by reference, because in a situation like this:
+ //
+ // <div style="display: table"><span></span></div>
+ //
+ // We have multiple wrappers to restyle (cell, row, table-row-group) and we
+ // want to add them in to the list all at once but restyle them using
+ // different ServoRestyleStates with different owners. When this situation
+ // occurs, the relevant frames will be placed in the array with ancestors
+ // before descendants.
+ nsTArray<nsIFrame*>& mPendingWrapperRestyles;
+
+ nsTArray<RefPtr<dom::Element>>& mPendingScrollAnchorSuppressions;
+
+ // Since we're given a possibly-nonempty mPendingWrapperRestyles to start
+ // with, we need to keep track of where the part of it we're responsible for
+ // starts.
+ size_t mPendingWrapperRestyleOffset;
+
+ const nsChangeHint mChangesHandled;
+
+ // We track the "owner" frame of this restyle state, that is, the frame that
+ // generated the last change that is stored in mChangesHandled, in order to
+ // verify that we only use mChangesHandled for actual descendants of that
+ // frame (given DOM order isn't always frame order, and that there are a few
+ // special cases for stuff like wrapper frames, ::backdrop, and so on).
+#ifdef DEBUG
+ const nsIFrame* mOwner{nullptr};
+#endif
+
+ // Whether we should assert in our destructor that we've processed all of the
+ // relevant wrapper restyles.
+#ifdef DEBUG
+ const bool mAssertWrapperRestyleLength;
+#endif // DEBUG
+};
+
+enum class ServoPostTraversalFlags : uint32_t;
+
+class RestyleManager {
+ friend class ServoStyleSet;
+
+ public:
+ typedef ServoElementSnapshotTable SnapshotTable;
+ typedef mozilla::dom::Element Element;
+
+ // Get an integer that increments every time we process pending restyles.
+ // The value is never 0.
+ uint64_t GetRestyleGeneration() const { return mRestyleGeneration; }
+ // Unlike GetRestyleGeneration, which means the actual restyling count,
+ // GetUndisplayedRestyleGeneration represents any possible DOM changes that
+ // can cause restyling. This is needed for getComputedStyle to work with
+ // non-styled (e.g. display: none) elements.
+ uint64_t GetUndisplayedRestyleGeneration() const {
+ return mUndisplayedRestyleGeneration;
+ }
+
+ void Disconnect() { mPresContext = nullptr; }
+
+ ~RestyleManager() {
+ MOZ_ASSERT(!mAnimationsWithDestroyedFrame,
+ "leaving dangling pointers from AnimationsWithDestroyedFrame");
+ MOZ_ASSERT(!mReentrantChanges);
+ }
+
+#ifdef DEBUG
+ static nsCString ChangeHintToString(nsChangeHint aHint);
+
+ /**
+ * DEBUG ONLY method to verify integrity of style tree versus frame tree
+ */
+ void DebugVerifyStyleTree(nsIFrame* aFrame);
+#endif
+
+ void FlushOverflowChangedTracker() { mOverflowChangedTracker.Flush(); }
+
+ // Should be called when a frame is going to be destroyed and
+ // WillDestroyFrameTree hasn't been called yet.
+ void NotifyDestroyingFrame(nsIFrame* aFrame) {
+ mOverflowChangedTracker.RemoveFrame(aFrame);
+ // If ProcessRestyledFrames is tracking frames which have been
+ // destroyed (to avoid re-visiting them), add this one to its set.
+ if (mDestroyedFrames) {
+ mDestroyedFrames->Insert(aFrame);
+ }
+ }
+
+ // Note: It's the caller's responsibility to make sure to wrap a
+ // ProcessRestyledFrames call in a view update batch and a script blocker.
+ // This function does not call ProcessAttachedQueue() on the binding manager.
+ // If the caller wants that to happen synchronously, it needs to handle that
+ // itself.
+ void ProcessRestyledFrames(nsStyleChangeList& aChangeList);
+
+ bool IsInStyleRefresh() const { return mInStyleRefresh; }
+
+ // AnimationsWithDestroyedFrame is used to stop animations and transitions
+ // on elements that have no frame at the end of the restyling process.
+ // It only lives during the restyling process.
+ class MOZ_STACK_CLASS AnimationsWithDestroyedFrame final {
+ public:
+ // Construct a AnimationsWithDestroyedFrame object. The caller must
+ // ensure that aRestyleManager lives at least as long as the
+ // object. (This is generally easy since the caller is typically a
+ // method of RestyleManager.)
+ explicit AnimationsWithDestroyedFrame(RestyleManager* aRestyleManager);
+
+ // This method takes the content node for the generated content for
+ // animation/transition on ::before and ::after, rather than the
+ // content node for the real element.
+ void Put(nsIContent* aContent, ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(aContent);
+ PseudoStyleType pseudoType = aComputedStyle->GetPseudoType();
+ if (pseudoType == PseudoStyleType::NotPseudo) {
+ mContents.AppendElement(aContent);
+ } else if (pseudoType == PseudoStyleType::before) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() ==
+ nsGkAtoms::mozgeneratedcontentbefore);
+ mBeforeContents.AppendElement(aContent->GetParent());
+ } else if (pseudoType == PseudoStyleType::after) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() ==
+ nsGkAtoms::mozgeneratedcontentafter);
+ mAfterContents.AppendElement(aContent->GetParent());
+ } else if (pseudoType == PseudoStyleType::marker) {
+ MOZ_ASSERT(aContent->NodeInfo()->NameAtom() ==
+ nsGkAtoms::mozgeneratedcontentmarker);
+ mMarkerContents.AppendElement(aContent->GetParent());
+ }
+ }
+
+ void StopAnimationsForElementsWithoutFrames();
+
+ private:
+ void StopAnimationsWithoutFrame(nsTArray<RefPtr<nsIContent>>& aArray,
+ PseudoStyleType aPseudoType);
+
+ RestyleManager* mRestyleManager;
+ AutoRestore<AnimationsWithDestroyedFrame*> mRestorePointer;
+
+ // Below three arrays might include elements that have already had their
+ // animations or transitions stopped.
+ //
+ // mBeforeContents, mAfterContents and mMarkerContents hold the real element
+ // rather than the content node for the generated content (which might
+ // change during a reframe)
+ nsTArray<RefPtr<nsIContent>> mContents;
+ nsTArray<RefPtr<nsIContent>> mBeforeContents;
+ nsTArray<RefPtr<nsIContent>> mAfterContents;
+ nsTArray<RefPtr<nsIContent>> mMarkerContents;
+ };
+
+ /**
+ * Return the current AnimationsWithDestroyedFrame struct, or null if we're
+ * not currently in a restyling operation.
+ */
+ AnimationsWithDestroyedFrame* GetAnimationsWithDestroyedFrame() {
+ return mAnimationsWithDestroyedFrame;
+ }
+
+ void ContentInserted(nsIContent* aChild);
+ void ContentAppended(nsIContent* aFirstNewContent);
+
+ // This would be have the same logic as RestyleForInsertOrChange if we got the
+ // notification before the removal. However, we get it after, so we need the
+ // following sibling in addition to the old child.
+ //
+ // aFollowingSibling is the sibling that used to come after aOldChild before
+ // the removal.
+ void ContentRemoved(nsIContent* aOldChild, nsIContent* aFollowingSibling);
+
+ // Restyling for a ContentInserted (notification after insertion) or
+ // for some CharacterDataChanged.
+ void RestyleForInsertOrChange(nsIContent* aChild);
+
+ // Restyle for a CharacterDataChanged notification. In practice this can only
+ // affect :empty / :-moz-only-whitespace / :-moz-first-node / :-moz-last-node.
+ void CharacterDataChanged(nsIContent*, const CharacterDataChangeInfo&);
+
+ void PostRestyleEvent(dom::Element*, RestyleHint,
+ nsChangeHint aMinChangeHint);
+
+ /**
+ * Posts restyle hints for animations.
+ * This is only called for the second traversal for CSS animations during
+ * updating CSS animations in a SequentialTask.
+ * This function does neither register a refresh observer nor flag that a
+ * style flush is needed since this function is supposed to be called during
+ * restyling process and this restyle event will be processed in the second
+ * traversal of the same restyling process.
+ */
+ void PostRestyleEventForAnimations(dom::Element*, PseudoStyleType,
+ RestyleHint);
+
+ void NextRestyleIsForCSSRuleChanges() { mRestyleForCSSRuleChanges = true; }
+
+ void RebuildAllStyleData(nsChangeHint aExtraHint, RestyleHint);
+
+ void ProcessPendingRestyles();
+ void ProcessAllPendingAttributeAndStateInvalidations();
+
+ void ElementStateChanged(Element*, dom::ElementState);
+
+ /**
+ * Posts restyle hints for siblings of an element and their descendants if the
+ * element's parent has NODE_HAS_SLOW_SELECTOR_NTH_OF and the element has a
+ * relevant state dependency.
+ */
+ void MaybeRestyleForNthOfState(ServoStyleSet& aStyleSet, dom::Element* aChild,
+ dom::ElementState aChangedBits);
+
+ void AttributeWillChange(Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType);
+ void ClassAttributeWillBeChangedBySMIL(dom::Element* aElement);
+ void AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue);
+
+ /**
+ * Restyle an element's previous and/or next siblings.
+ */
+ void RestyleSiblingsForNthOf(dom::Element* aChild,
+ NodeSelectorFlags aParentFlags);
+
+ /**
+ * Posts restyle hints for siblings of an element and their descendants if the
+ * element's parent has NODE_HAS_SLOW_SELECTOR_NTH_OF and the element has a
+ * relevant attribute dependency.
+ */
+ void MaybeRestyleForNthOfAttribute(dom::Element* aChild, nsAtom* aAttribute,
+ const nsAttrValue* aOldValue);
+
+ void MaybeRestyleForRelativeSelectorAttribute(dom::Element* aElement,
+ nsAtom* aAttribute,
+ const nsAttrValue* aOldValue);
+ void MaybeRestyleForRelativeSelectorState(ServoStyleSet& aStyleSet,
+ dom::Element* aElement,
+ dom::ElementState aChangedBits);
+
+ // This is only used to reparent things when moving them in/out of the
+ // ::first-line.
+ void ReparentComputedStyleForFirstLine(nsIFrame*);
+
+ /**
+ * Performs a Servo animation-only traversal to compute style for all nodes
+ * with the animation-only dirty bit in the document.
+ *
+ * This processes just the traversal for animation-only restyles and skips the
+ * normal traversal for other restyles unrelated to animations.
+ * This is used to bring throttled animations up-to-date such as when we need
+ * to get correct position for transform animations that are throttled because
+ * they are running on the compositor.
+ *
+ * This will traverse all of the document's style roots (that is, its document
+ * element, and the roots of the document-level native anonymous content).
+ */
+ void UpdateOnlyAnimationStyles();
+
+ // Get a counter that increments on every style change, that we use to
+ // track whether off-main-thread animations are up-to-date.
+ uint64_t GetAnimationGeneration() const { return mAnimationGeneration; }
+
+ // Typically only style frames have animations associated with them so this
+ // will likely return zero for anything that is not a style frame.
+ static uint64_t GetAnimationGenerationForFrame(nsIFrame* aStyleFrame);
+
+ // Update the animation generation count to mark that animation state
+ // has changed.
+ //
+ // This is normally performed automatically by ProcessPendingRestyles
+ // but it is also called when we have out-of-band changes to animations
+ // such as changes made through the Web Animations API.
+ void IncrementAnimationGeneration();
+
+ // Apply change hints for animations on the compositor.
+ //
+ // There are some cases where we forcibly apply change hints for animations
+ // even if there is no change hint produced in order to synchronize with
+ // animations running on the compositor.
+ //
+ // For example:
+ //
+ // a) Pausing animations via the Web Animations API
+ // b) When the style before sending the animation to the compositor exactly
+ // the same as the current style
+ static void AddLayerChangesForAnimation(
+ nsIFrame* aStyleFrame, nsIFrame* aPrimaryFrame, Element* aElement,
+ nsChangeHint aHintForThisFrame, nsStyleChangeList& aChangeListToProcess);
+
+ /**
+ * Whether to clear all the style data (including the element itself), or just
+ * the descendants' data.
+ */
+ enum class IncludeRoot {
+ Yes,
+ No,
+ };
+
+ /**
+ * Clears the ServoElementData and HasDirtyDescendants from all elements
+ * in the subtree rooted at aElement.
+ */
+ static void ClearServoDataFromSubtree(Element*,
+ IncludeRoot = IncludeRoot::Yes);
+
+ /**
+ * Clears HasDirtyDescendants and RestyleData from all elements in the
+ * subtree rooted at aElement.
+ */
+ static void ClearRestyleStateFromSubtree(Element* aElement);
+
+ explicit RestyleManager(nsPresContext* aPresContext);
+
+ protected:
+ /**
+ * Reparent the descendants of aFrame. This is used by ReparentComputedStyle
+ * and shouldn't be called by anyone else. aProviderChild, if non-null, is a
+ * child that was the style parent for aFrame and hence shouldn't be
+ * reparented.
+ */
+ void ReparentFrameDescendants(nsIFrame* aFrame, nsIFrame* aProviderChild,
+ ServoStyleSet& aStyleSet);
+
+ /**
+ * Performs post-Servo-traversal processing on this element and its
+ * descendants.
+ *
+ * Returns whether any style did actually change. There may be cases where we
+ * didn't need to change any style after all, for example, when a content
+ * attribute changes that happens not to have any effect on the style of that
+ * element or any descendant or sibling.
+ */
+ bool ProcessPostTraversal(Element* aElement, ServoRestyleState& aRestyleState,
+ ServoPostTraversalFlags aFlags);
+
+ struct TextPostTraversalState;
+ bool ProcessPostTraversalForText(nsIContent* aTextNode,
+ TextPostTraversalState& aState,
+ ServoRestyleState& aRestyleState,
+ ServoPostTraversalFlags aFlags);
+
+ ServoStyleSet* StyleSet() const { return PresContext()->StyleSet(); }
+
+ void RestylePreviousSiblings(nsIContent* aStartingSibling);
+ void RestyleSiblingsStartingWith(nsIContent* aStartingSibling);
+
+ void RestyleForEmptyChange(Element* aContainer);
+ void MaybeRestyleForEdgeChildChange(nsINode* aContainer,
+ nsIContent* aChangedChild);
+
+ bool IsDisconnected() const { return !mPresContext; }
+
+ void IncrementRestyleGeneration() {
+ if (++mRestyleGeneration == 0) {
+ // Keep mRestyleGeneration from being 0, since that's what
+ // nsPresContext::GetRestyleGeneration returns when it no
+ // longer has a RestyleManager.
+ ++mRestyleGeneration;
+ }
+ IncrementUndisplayedRestyleGeneration();
+ }
+
+ void IncrementUndisplayedRestyleGeneration() {
+ if (++mUndisplayedRestyleGeneration == 0) {
+ // Ensure mUndisplayedRestyleGeneration > 0, for the same reason as
+ // IncrementRestyleGeneration.
+ ++mUndisplayedRestyleGeneration;
+ }
+ }
+
+ nsPresContext* PresContext() const {
+ MOZ_ASSERT(mPresContext);
+ return mPresContext;
+ }
+
+ private:
+ nsPresContext* mPresContext; // weak, can be null after Disconnect().
+ uint64_t mRestyleGeneration;
+ uint64_t mUndisplayedRestyleGeneration;
+
+ // Used to keep track of frames that have been destroyed during
+ // ProcessRestyledFrames, so we don't try to touch them again even if
+ // they're referenced again later in the changelist.
+ mozilla::UniquePtr<nsTHashSet<const nsIFrame*>> mDestroyedFrames;
+
+ protected:
+ // True if we're in the middle of a nsRefreshDriver refresh
+ bool mInStyleRefresh;
+
+ // The total number of animation flushes by this frame constructor.
+ // Used to keep the layer and animation manager in sync.
+ uint64_t mAnimationGeneration;
+
+ OverflowChangedTracker mOverflowChangedTracker;
+
+ AnimationsWithDestroyedFrame* mAnimationsWithDestroyedFrame = nullptr;
+
+ const SnapshotTable& Snapshots() const { return mSnapshots; }
+ void ClearSnapshots();
+ ServoElementSnapshot& SnapshotFor(Element&);
+ void TakeSnapshotForAttributeChange(Element&, int32_t aNameSpaceID,
+ nsAtom* aAttribute);
+
+ void DoProcessPendingRestyles(ServoTraversalFlags aFlags);
+
+ // Function to do the actual (recursive) work of
+ // ReparentComputedStyleForFirstLine, once we have asserted the invariants
+ // that only hold on the initial call.
+ void DoReparentComputedStyleForFirstLine(nsIFrame*, ServoStyleSet&);
+
+ // We use a separate data structure from nsStyleChangeList because we need a
+ // frame to create nsStyleChangeList entries, and the primary frame may not be
+ // attached yet.
+ struct ReentrantChange {
+ nsCOMPtr<nsIContent> mContent;
+ nsChangeHint mHint;
+ };
+ typedef AutoTArray<ReentrantChange, 10> ReentrantChangeList;
+
+ // Only non-null while processing change hints. See the comment in
+ // ProcessPendingRestyles.
+ ReentrantChangeList* mReentrantChanges = nullptr;
+
+ // We use this flag to track if the current restyle contains any non-animation
+ // update, which triggers a normal restyle, and so there might be any new
+ // transition created later. Therefore, if this flag is true, we need to
+ // increase mAnimationGeneration before creating new transitions, so their
+ // creation sequence will be correct.
+ bool mHaveNonAnimationRestyles = false;
+
+ // Set to true when posting restyle events triggered by CSS rule changes.
+ // This flag is cleared once ProcessPendingRestyles has completed.
+ // When we process a traversal all descendants elements of the document
+ // triggered by CSS rule changes, we will need to update all elements with
+ // CSS animations. We propagate TraversalRestyleBehavior::ForCSSRuleChanges
+ // to traversal function if this flag is set.
+ bool mRestyleForCSSRuleChanges = false;
+
+ // A hashtable with the elements that have changed state or attributes, in
+ // order to calculate restyle hints during the traversal.
+ SnapshotTable mSnapshots;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ScrollStyles.cpp b/layout/base/ScrollStyles.cpp
new file mode 100644
index 0000000000..6ce21aad8c
--- /dev/null
+++ b/layout/base/ScrollStyles.cpp
@@ -0,0 +1,48 @@
+/* -*- 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 "mozilla/ScrollStyles.h"
+#include "nsStyleStruct.h" // for nsStyleDisplay
+
+namespace mozilla {
+
+// https://drafts.csswg.org/css-overflow/#overflow-propagation
+// "If `visible` is applied to the viewport, it must be interpreted as `auto`.
+// If `clip` is applied to the viewport, it must be interpreted as `hidden`."
+static StyleOverflow MapOverflowValueForViewportPropagation(
+ StyleOverflow aOverflow) {
+ switch (aOverflow) {
+ case StyleOverflow::Visible:
+ return StyleOverflow::Auto;
+ case StyleOverflow::Clip:
+ return StyleOverflow::Hidden;
+ default:
+ return aOverflow;
+ }
+}
+
+ScrollStyles::ScrollStyles(StyleOverflow aH, StyleOverflow aV)
+ : mHorizontal(aH), mVertical(aV) {
+ MOZ_ASSERT(mHorizontal == StyleOverflow::Auto ||
+ mHorizontal == StyleOverflow::Hidden ||
+ mHorizontal == StyleOverflow::Scroll);
+ MOZ_ASSERT(mVertical == StyleOverflow::Auto ||
+ mVertical == StyleOverflow::Hidden ||
+ mVertical == StyleOverflow::Scroll);
+}
+
+ScrollStyles::ScrollStyles(const nsStyleDisplay& aDisplay,
+ MapOverflowToValidScrollStyleTag)
+ : ScrollStyles(
+ MapOverflowValueForViewportPropagation(aDisplay.mOverflowX),
+ MapOverflowValueForViewportPropagation(aDisplay.mOverflowY)) {}
+
+bool ScrollStyles::IsHiddenInBothDirections() const {
+ return mHorizontal == StyleOverflow::Hidden &&
+ mVertical == StyleOverflow::Hidden;
+}
+
+} // namespace mozilla
diff --git a/layout/base/ScrollStyles.h b/layout/base/ScrollStyles.h
new file mode 100644
index 0000000000..473277a968
--- /dev/null
+++ b/layout/base/ScrollStyles.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/. */
+
+#ifndef mozilla_ScrollStyles_h
+#define mozilla_ScrollStyles_h
+
+#include <stdint.h>
+
+// Forward declarations
+struct nsStyleDisplay;
+
+namespace mozilla {
+
+enum class StyleOverflow : uint8_t;
+
+struct ScrollStyles {
+ // Always one of Scroll, Hidden, or Auto.
+ StyleOverflow mHorizontal;
+ StyleOverflow mVertical;
+
+ ScrollStyles(StyleOverflow aH, StyleOverflow aV);
+
+ // NOTE: This ctor maps `visible` to `auto` and `clip` to `hidden`.
+ // It's used for styles that are propagated from the <body> and for
+ // scroll frames (which we create also for overflow:clip/visible in
+ // some cases, e.g. form controls).
+ enum MapOverflowToValidScrollStyleTag { MapOverflowToValidScrollStyle };
+ ScrollStyles(const nsStyleDisplay&, MapOverflowToValidScrollStyleTag);
+
+ bool operator==(const ScrollStyles& aStyles) const {
+ return aStyles.mHorizontal == mHorizontal && aStyles.mVertical == mVertical;
+ }
+ bool operator!=(const ScrollStyles& aStyles) const {
+ return !(*this == aStyles);
+ }
+ bool IsHiddenInBothDirections() const;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollStyles_h
diff --git a/layout/base/ScrollTypes.h b/layout/base/ScrollTypes.h
new file mode 100644
index 0000000000..512c2579bc
--- /dev/null
+++ b/layout/base/ScrollTypes.h
@@ -0,0 +1,73 @@
+/* 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_ScrollTypes_h
+#define mozilla_ScrollTypes_h
+
+#include "mozilla/TypedEnumBits.h"
+
+// Types used in main-thread scrolling interfaces such as nsIScrollableFrame.
+
+namespace mozilla {
+
+/**
+ * Scroll modes for main-thread scroll operations. These are mostly used
+ * by nsIScrollableFrame methods.
+ *
+ * When a scroll operation is requested, we ask for instant, smooth,
+ * smooth msd, or normal scrolling.
+ *
+ * |Smooth| scrolls have a symmetrical acceleration and deceleration curve
+ * modeled with a set of splines that guarantee that the destination will be
+ * reached over a fixed time interval. |Smooth| will only be smooth if smooth
+ * scrolling is actually enabled. This behavior is utilized by keyboard and
+ * mouse wheel scrolling events.
+ *
+ * |SmoothMsd| implements a physically based model that approximates the
+ * behavior of a mass-spring-damper system. |SmoothMsd| scrolls have a
+ * non-symmetrical acceleration and deceleration curve, can potentially
+ * overshoot the destination on intermediate frames, and complete over a
+ * variable time interval. |SmoothMsd| will only be smooth if cssom-view
+ * smooth-scrolling is enabled.
+ *
+ * |Instant| is always synchronous, |Normal| can be asynchronous.
+ *
+ * If an |Instant| scroll request happens while a |Smooth| or async scroll is
+ * already in progress, the async scroll is interrupted and we instantly
+ * scroll to the destination.
+ *
+ * If an |Instant| or |Smooth| scroll request happens while a |SmoothMsd|
+ * scroll is already in progress, the |SmoothMsd| scroll is interrupted without
+ * first scrolling to the destination.
+ */
+enum class ScrollMode { Instant, Smooth, SmoothMsd, Normal };
+
+/**
+ * When scrolling by a relative amount, we can choose various units.
+ */
+enum class ScrollUnit { DEVICE_PIXELS, LINES, PAGES, WHOLE };
+
+/**
+ * Representing whether there's an on-going animation in APZC and it was
+ * triggered by script or by user input.
+ */
+enum class APZScrollAnimationType {
+ No, // No animation.
+ TriggeredByScript, // Animation triggered by script.
+ TriggeredByUserInput // Animation triggered by user input.
+};
+
+enum class ScrollSnapFlags : uint8_t {
+ Disabled = 0,
+ // https://drafts.csswg.org/css-scroll-snap/#intended-end-position
+ IntendedEndPosition = 1 << 0,
+ // https://drafts.csswg.org/css-scroll-snap/#intended-direction
+ IntendedDirection = 1 << 1
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ScrollSnapFlags);
+
+} // namespace mozilla
+
+#endif // mozilla_ScrollTypes_h
diff --git a/layout/base/ShapeUtils.cpp b/layout/base/ShapeUtils.cpp
new file mode 100644
index 0000000000..8c1eaa8a82
--- /dev/null
+++ b/layout/base/ShapeUtils.cpp
@@ -0,0 +1,249 @@
+/* -*- 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 "mozilla/ShapeUtils.h"
+
+#include <cstdlib>
+
+#include "nsCSSRendering.h"
+#include "nsLayoutUtils.h"
+#include "nsMargin.h"
+#include "nsStyleStruct.h"
+#include "mozilla/SVGContentUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/PathHelpers.h"
+
+namespace mozilla {
+
+nscoord ShapeUtils::ComputeShapeRadius(const StyleShapeRadius& aType,
+ const nscoord aCenter,
+ const nscoord aPosMin,
+ const nscoord aPosMax) {
+ MOZ_ASSERT(aType.IsFarthestSide() || aType.IsClosestSide());
+ nscoord dist1 = std::abs(aPosMin - aCenter);
+ nscoord dist2 = std::abs(aPosMax - aCenter);
+ nscoord length = 0;
+ if (aType.IsFarthestSide()) {
+ length = dist1 > dist2 ? dist1 : dist2;
+ } else {
+ length = dist1 > dist2 ? dist2 : dist1;
+ }
+ return length;
+}
+
+nsPoint ShapeUtils::ComputePosition(const StylePosition& aPosition,
+ const nsRect& aRefBox) {
+ nsPoint topLeft, anchor;
+ nsSize size(aRefBox.Size());
+ nsImageRenderer::ComputeObjectAnchorPoint(aPosition, size, size, &topLeft,
+ &anchor);
+ return anchor + aRefBox.TopLeft();
+}
+
+nsPoint ShapeUtils::ComputeCircleOrEllipseCenter(
+ const StyleBasicShape& aBasicShape, const nsRect& aRefBox) {
+ MOZ_ASSERT(aBasicShape.IsCircle() || aBasicShape.IsEllipse(),
+ "The basic shape must be circle() or ellipse!");
+
+ const auto& position = aBasicShape.IsCircle()
+ ? aBasicShape.AsCircle().position
+ : aBasicShape.AsEllipse().position;
+ // If position is not specified, we use 50% 50%.
+ if (position.IsAuto()) {
+ return ComputePosition(StylePosition::FromPercentage(0.5), aRefBox);
+ }
+
+ MOZ_ASSERT(position.IsPosition());
+ return ComputePosition(position.AsPosition(), aRefBox);
+}
+
+nscoord ShapeUtils::ComputeCircleRadius(const StyleBasicShape& aBasicShape,
+ const nsPoint& aCenter,
+ const nsRect& aRefBox) {
+ MOZ_ASSERT(aBasicShape.IsCircle(), "The basic shape must be circle()!");
+ const auto& radius = aBasicShape.AsCircle().radius;
+ if (radius.IsLength()) {
+ return radius.AsLength().Resolve([&] {
+ // We resolve percent <shape-radius> value for circle() as defined here:
+ // https://drafts.csswg.org/css-shapes/#funcdef-circle
+ double referenceLength = SVGContentUtils::ComputeNormalizedHypotenuse(
+ aRefBox.width, aRefBox.height);
+ return NSToCoordRound(referenceLength);
+ });
+ }
+
+ nscoord horizontal =
+ ComputeShapeRadius(radius, aCenter.x, aRefBox.x, aRefBox.XMost());
+ nscoord vertical =
+ ComputeShapeRadius(radius, aCenter.y, aRefBox.y, aRefBox.YMost());
+ return radius.IsFarthestSide() ? std::max(horizontal, vertical)
+ : std::min(horizontal, vertical);
+}
+
+nsSize ShapeUtils::ComputeEllipseRadii(const StyleBasicShape& aBasicShape,
+ const nsPoint& aCenter,
+ const nsRect& aRefBox) {
+ MOZ_ASSERT(aBasicShape.IsEllipse(), "The basic shape must be ellipse()!");
+ const auto& ellipse = aBasicShape.AsEllipse();
+ nsSize radii;
+ if (ellipse.semiaxis_x.IsLength()) {
+ radii.width = ellipse.semiaxis_x.AsLength().Resolve(aRefBox.width);
+ } else {
+ radii.width = ComputeShapeRadius(ellipse.semiaxis_x, aCenter.x, aRefBox.x,
+ aRefBox.XMost());
+ }
+
+ if (ellipse.semiaxis_y.IsLength()) {
+ radii.height = ellipse.semiaxis_y.AsLength().Resolve(aRefBox.height);
+ } else {
+ radii.height = ComputeShapeRadius(ellipse.semiaxis_y, aCenter.y, aRefBox.y,
+ aRefBox.YMost());
+ }
+
+ return radii;
+}
+
+/* static */
+nsRect ShapeUtils::ComputeInsetRect(
+ const StyleRect<LengthPercentage>& aStyleRect, const nsRect& aRefBox) {
+ nsMargin inset(aStyleRect._0.Resolve(aRefBox.Height()),
+ aStyleRect._1.Resolve(aRefBox.Width()),
+ aStyleRect._2.Resolve(aRefBox.Height()),
+ aStyleRect._3.Resolve(aRefBox.Width()));
+
+ nscoord x = aRefBox.X() + inset.left;
+ nscoord width = aRefBox.Width() - inset.LeftRight();
+ nscoord y = aRefBox.Y() + inset.top;
+ nscoord height = aRefBox.Height() - inset.TopBottom();
+
+ // Invert left and right, if necessary.
+ if (width < 0) {
+ width *= -1;
+ x -= width;
+ }
+
+ // Invert top and bottom, if necessary.
+ if (height < 0) {
+ height *= -1;
+ y -= height;
+ }
+
+ return nsRect(x, y, width, height);
+}
+
+/* static */
+bool ShapeUtils::ComputeRectRadii(const StyleBorderRadius& aBorderRadius,
+ const nsRect& aRefBox, const nsRect& aRect,
+ nscoord aRadii[8]) {
+ return nsIFrame::ComputeBorderRadii(aBorderRadius, aRefBox.Size(),
+ aRect.Size(), Sides(), aRadii);
+}
+
+/* static */
+nsTArray<nsPoint> ShapeUtils::ComputePolygonVertices(
+ const StyleBasicShape& aBasicShape, const nsRect& aRefBox) {
+ MOZ_ASSERT(aBasicShape.IsPolygon(), "The basic shape must be polygon()!");
+
+ auto coords = aBasicShape.AsPolygon().coordinates.AsSpan();
+ nsTArray<nsPoint> vertices(coords.Length());
+ for (const StylePolygonCoord<LengthPercentage>& point : coords) {
+ vertices.AppendElement(nsPoint(point._0.Resolve(aRefBox.width),
+ point._1.Resolve(aRefBox.height)) +
+ aRefBox.TopLeft());
+ }
+ return vertices;
+}
+
+/* static */
+static inline gfx::Point ConvertToGfxPoint(const nsPoint& aPoint,
+ nscoord aAppUnitsPerPixel) {
+ return {static_cast<gfx::Float>(aPoint.x) /
+ static_cast<gfx::Float>(aAppUnitsPerPixel),
+ static_cast<gfx::Float>(aPoint.y) /
+ static_cast<gfx::Float>(aAppUnitsPerPixel)};
+}
+
+/* static */
+already_AddRefed<gfx::Path> ShapeUtils::BuildCirclePath(
+ const StyleBasicShape& aShape, const nsRect& aRefBox,
+ const nsPoint& aCenter, nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder* aPathBuilder) {
+ const nscoord r = ComputeCircleRadius(aShape, aCenter, aRefBox);
+ aPathBuilder->Arc(
+ ConvertToGfxPoint(aCenter, aAppUnitsPerPixel),
+ static_cast<float>(r) / static_cast<float>(aAppUnitsPerPixel), 0.0,
+ gfx::Float(2.0 * M_PI));
+ aPathBuilder->Close();
+ return aPathBuilder->Finish();
+}
+
+static inline gfx::Size ConvertToGfxSize(const nsSize& aSize,
+ nscoord aAppUnitsPerPixel) {
+ return {static_cast<gfx::Float>(aSize.width) /
+ static_cast<gfx::Float>(aAppUnitsPerPixel),
+ static_cast<gfx::Float>(aSize.height) /
+ static_cast<gfx::Float>(aAppUnitsPerPixel)};
+}
+
+/* static */
+already_AddRefed<gfx::Path> ShapeUtils::BuildEllipsePath(
+ const StyleBasicShape& aShape, const nsRect& aRefBox,
+ const nsPoint& aCenter, nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder* aPathBuilder) {
+ const nsSize radii = ComputeEllipseRadii(aShape, aCenter, aRefBox);
+ EllipseToBezier(aPathBuilder, ConvertToGfxPoint(aCenter, aAppUnitsPerPixel),
+ ConvertToGfxSize(radii, aAppUnitsPerPixel));
+ aPathBuilder->Close();
+ return aPathBuilder->Finish();
+}
+
+/* static */
+already_AddRefed<gfx::Path> ShapeUtils::BuildPolygonPath(
+ const StyleBasicShape& aShape, const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
+ nsTArray<nsPoint> vertices = ComputePolygonVertices(aShape, aRefBox);
+ if (vertices.IsEmpty()) {
+ MOZ_ASSERT_UNREACHABLE(
+ "ComputePolygonVertices() should've given us some vertices!");
+ } else {
+ aPathBuilder->MoveTo(NSPointToPoint(vertices[0], aAppUnitsPerPixel));
+ for (size_t i = 1; i < vertices.Length(); ++i) {
+ aPathBuilder->LineTo(NSPointToPoint(vertices[i], aAppUnitsPerPixel));
+ }
+ }
+ aPathBuilder->Close();
+ return aPathBuilder->Finish();
+}
+
+/* static */
+already_AddRefed<gfx::Path> ShapeUtils::BuildInsetPath(
+ const StyleBasicShape& aShape, const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
+ const nsRect insetRect = ComputeInsetRect(aShape.AsRect().rect, aRefBox);
+ nscoord appUnitsRadii[8];
+ const bool hasRadii = ComputeRectRadii(aShape.AsRect().round, aRefBox,
+ insetRect, appUnitsRadii);
+ return BuildRectPath(insetRect, hasRadii ? appUnitsRadii : nullptr, aRefBox,
+ aAppUnitsPerPixel, aPathBuilder);
+}
+
+/* static */
+already_AddRefed<gfx::Path> ShapeUtils::BuildRectPath(
+ const nsRect& aRect, const nscoord aRadii[8], const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel, gfx::PathBuilder* aPathBuilder) {
+ const gfx::Rect insetRectPixels = NSRectToRect(aRect, aAppUnitsPerPixel);
+ if (aRadii) {
+ gfx::RectCornerRadii corners;
+ nsCSSRendering::ComputePixelRadii(aRadii, aAppUnitsPerPixel, &corners);
+
+ AppendRoundedRectToPath(aPathBuilder, insetRectPixels, corners, true);
+ } else {
+ AppendRectToPath(aPathBuilder, insetRectPixels, true);
+ }
+ return aPathBuilder->Finish();
+}
+
+} // namespace mozilla
diff --git a/layout/base/ShapeUtils.h b/layout/base/ShapeUtils.h
new file mode 100644
index 0000000000..972fa4efb3
--- /dev/null
+++ b/layout/base/ShapeUtils.h
@@ -0,0 +1,151 @@
+/* -*- 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_ShapeUtils_h
+#define mozilla_ShapeUtils_h
+
+#include "nsCoord.h"
+#include "nsSize.h"
+#include "nsStyleConsts.h"
+#include "nsTArray.h"
+
+struct nsPoint;
+struct nsRect;
+
+namespace mozilla {
+namespace gfx {
+class Path;
+class PathBuilder;
+} // namespace gfx
+
+// ShapeUtils is a namespace class containing utility functions related to
+// processing basic shapes in the CSS Shapes Module.
+// https://drafts.csswg.org/css-shapes/#basic-shape-functions
+//
+struct ShapeUtils final {
+ // Compute the length of a keyword <shape-radius>, i.e. closest-side or
+ // farthest-side, for a circle or an ellipse on a single dimension. The
+ // caller needs to call for both dimensions and combine the result.
+ // https://drafts.csswg.org/css-shapes/#typedef-shape-radius.
+ // @return The length of the radius in app units.
+ static nscoord ComputeShapeRadius(const StyleShapeRadius& aType,
+ const nscoord aCenter,
+ const nscoord aPosMin,
+ const nscoord aPosMax);
+
+ // Compute the position based on |aRefBox|.
+ // @param aRefBox The reference box for the position.
+ // @return The point inside |aRefBox|.
+ static nsPoint ComputePosition(const StylePosition&, const nsRect&);
+
+ // Compute the center of a circle or an ellipse.
+ // @param aRefBox The reference box of the basic shape.
+ // @return The point of the center.
+ static nsPoint ComputeCircleOrEllipseCenter(const StyleBasicShape&,
+ const nsRect& aRefBox);
+
+ // Compute the radius for a circle.
+ // @param aCenter the center of the circle.
+ // @param aRefBox the reference box of the circle.
+ // @return The length of the radius in app units.
+ static nscoord ComputeCircleRadius(const StyleBasicShape&,
+ const nsPoint& aCenter,
+ const nsRect& aRefBox);
+
+ // Compute the radii for an ellipse.
+ // @param aCenter the center of the ellipse.
+ // @param aRefBox the reference box of the ellipse.
+ // @return The radii of the ellipse in app units. The width and height
+ // represent the x-axis and y-axis radii of the ellipse.
+ static nsSize ComputeEllipseRadii(const StyleBasicShape&,
+ const nsPoint& aCenter,
+ const nsRect& aRefBox);
+
+ // Compute the rect for an inset()/xywh()/rect().
+ // If the inset amount is larger than aRefBox itself, this will return a rect
+ // the same shape as the inverse rect that would be created by insetting
+ // aRefBox by the inset amount. This process is *not* what is called for by
+ // the current spec at
+ // https://drafts.csswg.org/css-shapes-1/#supported-basic-shapes.
+ //
+ // The spec currently treats empty shapes, including overly-inset rects, as
+ // defining 'empty float areas' that don't affect layout. However, it is
+ // practically useful to treat empty shapes as having edges for purposes of
+ // affecting layout, and there is growing momentum for the approach we
+ // are taking here.
+ // @param aRefBox the reference box of the inset/xywh/rect.
+ // @return The inset/xywh/rect rect in app units.
+ static nsRect ComputeInsetRect(const StyleRect<LengthPercentage>& aStyleRect,
+ const nsRect& aRefBox);
+
+ // Compute the radii for a rectanglar shape, i.e. inset()/xywh()/rect().
+ // @param aRefBox the reference box of the rect.
+ // @param aRect the rect we computed from Compute{Inset}Rect(), in app units.
+ // @param aRadii the returned radii in app units.
+ // @return true if any of the radii is nonzero; false otherwise.
+ static bool ComputeRectRadii(const StyleBorderRadius&, const nsRect& aRefBox,
+ const nsRect& aRect, nscoord aRadii[8]);
+
+ // Compute the vertices for a polygon.
+ // @param aRefBox the reference box of the polygon.
+ // @return The vertices in app units; the coordinate space is the same
+ // as aRefBox.
+ static nsTArray<nsPoint> ComputePolygonVertices(const StyleBasicShape&,
+ const nsRect& aRefBox);
+
+ // Compute a gfx::path from a circle.
+ // @param aRefBox the reference box of the circle.
+ // @param aCenter the center point of the circle.
+ // @return The gfx::Path of this circle.
+ static already_AddRefed<gfx::Path> BuildCirclePath(const StyleBasicShape&,
+ const nsRect& aRefBox,
+ const nsPoint& aCenter,
+ nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder*);
+
+ // Compute a gfx::path from an ellipse.
+ // @param aRefBox the reference box of the ellipse.
+ // @param aCenter the center point of the ellipse.
+ // @return The gfx::Path of this ellipse.
+ static already_AddRefed<gfx::Path> BuildEllipsePath(const StyleBasicShape&,
+ const nsRect& aRefBox,
+ const nsPoint& aCenter,
+ nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder*);
+
+ // Compute a gfx::path from a polygon.
+ // @param aRefBox the reference box of the polygon.
+ // @return The gfx::Path of this polygon.
+ static already_AddRefed<gfx::Path> BuildPolygonPath(const StyleBasicShape&,
+ const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder*);
+
+ // Compute a gfx::path from a StyleBasicShape which is an inset.
+ // @param aRefBox the reference box of the inset.
+ // @return The gfx::Path of this inset.
+ static already_AddRefed<gfx::Path> BuildInsetPath(const StyleBasicShape&,
+ const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder*);
+
+ // Compute a gfx::path from a rectanglar shape (i.e. inset()/xywh()/rect())
+ // and the round radii.
+ // @param aRect the rect we computed from Compute{Inset}Rect().
+ // @param aRadii the radii of the rect. It should be an array with length 8.
+ // If it's nullptr, we don't have the valid radii.
+ // @param aRefBox the reference box of the rect.
+ // @return The gfx::Path of this rect.
+ static already_AddRefed<gfx::Path> BuildRectPath(const nsRect& aRect,
+ const nscoord aRadii[8],
+ const nsRect& aRefBox,
+ nscoord aAppUnitsPerPixel,
+ gfx::PathBuilder*);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ShapeUtils_h
diff --git a/layout/base/StackArena.cpp b/layout/base/StackArena.cpp
new file mode 100644
index 0000000000..3c4717ef02
--- /dev/null
+++ b/layout/base/StackArena.cpp
@@ -0,0 +1,172 @@
+/* -*- 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 "StackArena.h"
+#include "nsAlgorithm.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+
+// A block of memory that the stack will chop up and hand out.
+struct StackBlock {
+ // Subtract sizeof(StackBlock*) to give space for the |mNext| field.
+ static const size_t MAX_USABLE_SIZE = 4096 - sizeof(StackBlock*);
+
+ // A block of memory.
+ char mBlock[MAX_USABLE_SIZE];
+
+ // Another block of memory that would only be created if our stack
+ // overflowed.
+ StackBlock* mNext;
+
+ StackBlock() : mNext(nullptr) {}
+ ~StackBlock() = default;
+};
+
+static_assert(sizeof(StackBlock) == 4096, "StackBlock must be 4096 bytes");
+
+// We hold an array of marks. A push pushes a mark on the stack.
+// A pop pops it off.
+struct StackMark {
+ // The block of memory from which we are currently handing out chunks.
+ StackBlock* mBlock;
+
+ // Our current position in the block.
+ size_t mPos;
+};
+
+StackArena* AutoStackArena::gStackArena;
+
+StackArena::StackArena() {
+ mMarkLength = 0;
+ mMarks = nullptr;
+
+ // Allocate our stack memory.
+ mBlocks = new StackBlock();
+ mCurBlock = mBlocks;
+
+ mStackTop = 0;
+ mPos = 0;
+}
+
+StackArena::~StackArena() {
+ // Free up our data.
+ delete[] mMarks;
+ while (mBlocks) {
+ StackBlock* toDelete = mBlocks;
+ mBlocks = mBlocks->mNext;
+ delete toDelete;
+ }
+}
+
+size_t StackArena::SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = 0;
+ StackBlock* block = mBlocks;
+ while (block) {
+ n += aMallocSizeOf(block);
+ block = block->mNext;
+ }
+ n += aMallocSizeOf(mMarks);
+ return n;
+}
+
+static const int STACK_ARENA_MARK_INCREMENT = 50;
+
+void StackArena::Push() {
+ // Resize the mark array if we overrun it. Failure to allocate the
+ // mark array is not fatal; we just won't free to that mark. This
+ // allows callers not to worry about error checking.
+ if (mStackTop >= mMarkLength) {
+ uint32_t newLength = mStackTop + STACK_ARENA_MARK_INCREMENT;
+ StackMark* newMarks = new StackMark[newLength];
+ if (newMarks) {
+ if (mMarkLength) {
+ memcpy(newMarks, mMarks, sizeof(StackMark) * mMarkLength);
+ }
+ // Fill in any marks that we couldn't allocate during a prior call
+ // to Push().
+ for (; mMarkLength < mStackTop; ++mMarkLength) {
+ MOZ_ASSERT_UNREACHABLE("should only hit this on out-of-memory");
+ newMarks[mMarkLength].mBlock = mCurBlock;
+ newMarks[mMarkLength].mPos = mPos;
+ }
+ delete[] mMarks;
+ mMarks = newMarks;
+ mMarkLength = newLength;
+ }
+ }
+
+ // Set a mark at the top (if we can).
+ NS_ASSERTION(mStackTop < mMarkLength, "out of memory");
+ if (mStackTop < mMarkLength) {
+ mMarks[mStackTop].mBlock = mCurBlock;
+ mMarks[mStackTop].mPos = mPos;
+ }
+
+ mStackTop++;
+}
+
+void* StackArena::Allocate(size_t aSize) {
+ NS_ASSERTION(mStackTop > 0, "Allocate called without Push");
+
+ // Align to a multiple of 8.
+ aSize = NS_ROUNDUP<size_t>(aSize, 8);
+
+ // On stack overflow, grab another block.
+ if (mPos + aSize >= StackBlock::MAX_USABLE_SIZE) {
+ NS_ASSERTION(aSize <= StackBlock::MAX_USABLE_SIZE,
+ "Requested memory is greater that our block size!!");
+ if (mCurBlock->mNext == nullptr) {
+ mCurBlock->mNext = new StackBlock();
+ }
+
+ mCurBlock = mCurBlock->mNext;
+ mPos = 0;
+ }
+
+ // Return the chunk they need.
+ void* result = mCurBlock->mBlock + mPos;
+ mPos += aSize;
+
+ return result;
+}
+
+void StackArena::Pop() {
+ // Pop off the mark.
+ NS_ASSERTION(mStackTop > 0, "unmatched pop");
+ mStackTop--;
+
+ if (mStackTop >= mMarkLength) {
+ // We couldn't allocate the marks array at the time of the push, so
+ // we don't know where we're freeing to.
+ MOZ_ASSERT_UNREACHABLE("out of memory");
+ if (mStackTop == 0) {
+ // But we do know if we've completely pushed the stack.
+ mCurBlock = mBlocks;
+ mPos = 0;
+ }
+ return;
+ }
+
+#ifdef DEBUG
+ // Mark the "freed" memory with 0xdd to help with debugging of memory
+ // allocation problems.
+ {
+ StackBlock *block = mMarks[mStackTop].mBlock, *block_end = mCurBlock;
+ size_t pos = mMarks[mStackTop].mPos;
+ for (; block != block_end; block = block->mNext, pos = 0) {
+ memset(block->mBlock + pos, 0xdd, sizeof(block->mBlock) - pos);
+ }
+ memset(block->mBlock + pos, 0xdd, mPos - pos);
+ }
+#endif
+
+ mCurBlock = mMarks[mStackTop].mBlock;
+ mPos = mMarks[mStackTop].mPos;
+}
+
+} // namespace mozilla
diff --git a/layout/base/StackArena.h b/layout/base/StackArena.h
new file mode 100644
index 0000000000..bb9d205a74
--- /dev/null
+++ b/layout/base/StackArena.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 StackArena_h
+#define StackArena_h
+
+#include "nsError.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MemoryReporting.h"
+
+namespace mozilla {
+
+struct StackBlock;
+struct StackMark;
+class AutoStackArena;
+
+// Private helper class for AutoStackArena.
+class StackArena {
+ private:
+ friend class AutoStackArena;
+ StackArena();
+ ~StackArena();
+
+ // Memory management functions.
+ void* Allocate(size_t aSize);
+ void Push();
+ void Pop();
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ // Our current position in memory.
+ size_t mPos;
+
+ // A list of memory blocks. Usually there is only one
+ // but if we overrun our stack size we can get more memory.
+ StackBlock* mBlocks;
+
+ // The current block.
+ StackBlock* mCurBlock;
+
+ // Our stack of mark where push has been called.
+ StackMark* mMarks;
+
+ // The current top of the mark list.
+ uint32_t mStackTop;
+
+ // The size of the mark array.
+ uint32_t mMarkLength;
+};
+
+// Class for stack scoped arena memory allocations.
+//
+// Callers who wish to allocate memory whose lifetime corresponds to the
+// lifetime of a stack-allocated object can use this class. First,
+// declare an AutoStackArena object on the stack. Then all subsequent
+// calls to Allocate will allocate memory from an arena pool that will
+// be freed when that variable goes out of scope. Nesting is allowed.
+//
+// Individual allocations cannot exceed StackBlock::MAX_USABLE_SIZE
+// bytes.
+//
+class MOZ_RAII AutoStackArena {
+ public:
+ AutoStackArena() : mOwnsStackArena(false) {
+ if (!gStackArena) {
+ gStackArena = new StackArena();
+ mOwnsStackArena = true;
+ }
+ gStackArena->Push();
+ }
+
+ ~AutoStackArena() {
+ gStackArena->Pop();
+ if (mOwnsStackArena) {
+ delete gStackArena;
+ gStackArena = nullptr;
+ }
+ }
+
+ static void* Allocate(size_t aSize) { return gStackArena->Allocate(aSize); }
+
+ private:
+ static StackArena* gStackArena;
+ bool mOwnsStackArena;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/StaticPresData.cpp b/layout/base/StaticPresData.cpp
new file mode 100644
index 0000000000..4307528e0b
--- /dev/null
+++ b/layout/base/StaticPresData.cpp
@@ -0,0 +1,263 @@
+/* -*- 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 "mozilla/StaticPresData.h"
+
+#include "gfxFontFeatures.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoUtils.h"
+#include "mozilla/StaticPtr.h"
+#include "nsPresContext.h"
+
+namespace mozilla {
+
+static StaticAutoPtr<StaticPresData> sSingleton;
+
+void StaticPresData::Init() {
+ MOZ_ASSERT(!sSingleton);
+ sSingleton = new StaticPresData();
+}
+
+void StaticPresData::Shutdown() {
+ MOZ_ASSERT(sSingleton);
+ sSingleton = nullptr;
+}
+
+StaticPresData* StaticPresData::Get() {
+ MOZ_ASSERT(sSingleton);
+ return sSingleton;
+}
+
+StaticPresData::StaticPresData() {
+ mLangService = nsLanguageAtomService::GetService();
+}
+
+#define MAKE_FONT_PREF_KEY(_pref, _s0, _s1) \
+ _pref.Assign(_s0); \
+ _pref.Append(_s1);
+
+// clang-format off
+static const char* const kGenericFont[] = {
+ ".variable.",
+ ".serif.",
+ ".sans-serif.",
+ ".monospace.",
+ ".cursive.",
+ ".fantasy.",
+ ".system-ui.",
+};
+// clang-format on
+
+enum class DefaultFont {
+ Variable = 0,
+ Serif,
+ SansSerif,
+ Monospace,
+ Cursive,
+ Fantasy,
+ SystemUi,
+ COUNT
+};
+
+void LangGroupFontPrefs::Initialize(nsStaticAtom* aLangGroupAtom) {
+ mLangGroup = aLangGroupAtom;
+
+ /* Fetch the font prefs to be used -- see bug 61883 for details.
+ Not all prefs are needed upfront. Some are fallback prefs intended
+ for the GFX font sub-system...
+
+ -- attributes for generic fonts --------------------------------------
+
+ font.default.[langGroup] = serif | sans-serif
+ fallback generic font
+
+ font.name.[generic].[langGroup]
+ current user' selected font on the pref dialog
+
+ font.name-list.[generic].[langGroup] = fontname1, fontname2, ...
+ [factory pre-built list]
+
+ font.size.[generic].[langGroup] = integer
+ settable by the user
+
+ font.size-adjust.[generic].[langGroup] = "float"
+ settable by the user
+
+ font.minimum-size.[langGroup] = integer
+ settable by the user
+ */
+
+ nsAutoCString langGroup;
+ aLangGroupAtom->ToUTF8String(langGroup);
+
+ mDefaultVariableFont.size = Length::FromPixels(16.0f);
+ mDefaultMonospaceFont.size = Length::FromPixels(13.0f);
+
+ nsAutoCString pref;
+
+ // get font.minimum-size.[langGroup]
+
+ MAKE_FONT_PREF_KEY(pref, "font.minimum-size.", langGroup);
+
+ int32_t size = Preferences::GetInt(pref.get());
+ mMinimumFontSize = Length::FromPixels(size);
+
+ // clang-format off
+ nsFont* fontTypes[] = {
+ &mDefaultVariableFont,
+ &mDefaultSerifFont,
+ &mDefaultSansSerifFont,
+ &mDefaultMonospaceFont,
+ &mDefaultCursiveFont,
+ &mDefaultFantasyFont,
+ &mDefaultSystemUiFont,
+ };
+ // clang-format on
+ static_assert(MOZ_ARRAY_LENGTH(fontTypes) == size_t(DefaultFont::COUNT),
+ "FontTypes array count is not correct");
+
+ // Get attributes specific to each generic font. We do not get the user's
+ // generic-font-name-to-specific-family-name preferences because its the
+ // generic name that should be fed into the cascade. It is up to the GFX
+ // code to look up the font prefs to convert generic names to specific
+ // family names as necessary.
+ nsAutoCString generic_dot_langGroup;
+ for (auto type : MakeEnumeratedRange(DefaultFont::COUNT)) {
+ generic_dot_langGroup.Assign(kGenericFont[size_t(type)]);
+ generic_dot_langGroup.Append(langGroup);
+
+ nsFont* font = fontTypes[size_t(type)];
+
+ // Set the default variable font (the other fonts are seen as 'generic'
+ // fonts in GFX and will be queried there when hunting for alternative
+ // fonts)
+ if (type == DefaultFont::Variable) {
+ // XXX "font.name.variable."? There is no such pref...
+ MAKE_FONT_PREF_KEY(pref, "font.name.variable.", langGroup);
+
+ nsAutoCString value;
+ Preferences::GetCString(pref.get(), value);
+ if (value.IsEmpty()) {
+ MAKE_FONT_PREF_KEY(pref, "font.default.", langGroup);
+ Preferences::GetCString(pref.get(), value);
+ }
+ if (!value.IsEmpty()) {
+ auto defaultVariableName = StyleSingleFontFamily::Parse(value);
+ auto defaultType = defaultVariableName.IsGeneric()
+ ? defaultVariableName.AsGeneric()
+ : StyleGenericFontFamily::None;
+ if (defaultType == StyleGenericFontFamily::Serif ||
+ defaultType == StyleGenericFontFamily::SansSerif) {
+ mDefaultVariableFont.family = *Servo_FontFamily_Generic(defaultType);
+ } else {
+ NS_WARNING("default type must be serif or sans-serif");
+ }
+ }
+ } else {
+ if (type != DefaultFont::Monospace) {
+ // all the other generic fonts are initialized with the size of the
+ // variable font, but their specific size can supersede later -- see
+ // below
+ font->size = mDefaultVariableFont.size;
+ }
+ }
+
+ // Bug 84398: for spec purists, a different font-size only applies to the
+ // .variable. and .fixed. fonts and the other fonts should get
+ // |font-size-adjust|. The problem is that only GfxWin has the support for
+ // |font-size-adjust|. So for parity, we enable the ability to set a
+ // different font-size on all platforms.
+
+ // get font.size.[generic].[langGroup]
+ // size=0 means 'Auto', i.e., generic fonts retain the size of the variable
+ // font
+ MAKE_FONT_PREF_KEY(pref, "font.size", generic_dot_langGroup);
+ size = Preferences::GetInt(pref.get());
+ if (size > 0) {
+ font->size = Length::FromPixels(size);
+ }
+
+ // get font.size-adjust.[generic].[langGroup]
+ // XXX only applicable on GFX ports that handle |font-size-adjust|
+ MAKE_FONT_PREF_KEY(pref, "font.size-adjust", generic_dot_langGroup);
+ nsAutoCString cvalue;
+ Preferences::GetCString(pref.get(), cvalue);
+ if (!cvalue.IsEmpty()) {
+ font->sizeAdjust =
+ StyleFontSizeAdjust::ExHeight(float(atof(cvalue.get())));
+ }
+
+#ifdef DEBUG_rbs
+ printf("%s Family-list:%s size:%d sizeAdjust:%.2f\n",
+ generic_dot_langGroup.get(), NS_ConvertUTF16toUTF8(font->name).get(),
+ font->size, font->sizeAdjust);
+#endif
+ }
+}
+
+nsStaticAtom* StaticPresData::GetLangGroup(nsAtom* aLanguage,
+ bool* aNeedsToCache) const {
+ nsStaticAtom* langGroupAtom =
+ mLangService->GetLanguageGroup(aLanguage, aNeedsToCache);
+ // Assume x-western is safe...
+ return langGroupAtom ? langGroupAtom : nsGkAtoms::x_western;
+}
+
+nsStaticAtom* StaticPresData::GetUncachedLangGroup(nsAtom* aLanguage) const {
+ nsStaticAtom* langGroupAtom =
+ mLangService->GetUncachedLanguageGroup(aLanguage);
+ return langGroupAtom ? langGroupAtom : nsGkAtoms::x_western;
+}
+
+const LangGroupFontPrefs* StaticPresData::GetFontPrefsForLang(
+ nsAtom* aLanguage, bool* aNeedsToCache) {
+ // Get language group for aLanguage:
+ MOZ_ASSERT(aLanguage);
+ MOZ_ASSERT(mLangService);
+
+ nsStaticAtom* langGroupAtom = GetLangGroup(aLanguage, aNeedsToCache);
+ if (aNeedsToCache && *aNeedsToCache) {
+ return nullptr;
+ }
+
+ if (!aNeedsToCache) {
+ AssertIsMainThreadOrServoFontMetricsLocked();
+ }
+
+ LangGroupFontPrefs* prefs = &mLangGroupFontPrefs;
+ if (prefs->mLangGroup) { // if initialized
+ DebugOnly<uint32_t> count = 0;
+ for (;;) {
+ if (prefs->mLangGroup == langGroupAtom) {
+ return prefs;
+ }
+ if (!prefs->mNext) {
+ break;
+ }
+ prefs = prefs->mNext.get();
+ }
+ if (aNeedsToCache) {
+ *aNeedsToCache = true;
+ return nullptr;
+ }
+ // nothing cached, so go on and fetch the prefs for this lang group:
+ prefs->mNext = MakeUnique<LangGroupFontPrefs>();
+ prefs = prefs->mNext.get();
+ }
+
+ if (aNeedsToCache) {
+ *aNeedsToCache = true;
+ return nullptr;
+ }
+
+ AssertIsMainThreadOrServoFontMetricsLocked();
+ prefs->Initialize(langGroupAtom);
+
+ return prefs;
+}
+
+} // namespace mozilla
diff --git a/layout/base/StaticPresData.h b/layout/base/StaticPresData.h
new file mode 100644
index 0000000000..9476376796
--- /dev/null
+++ b/layout/base/StaticPresData.h
@@ -0,0 +1,176 @@
+/* -*- 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_StaticPresData_h
+#define mozilla_StaticPresData_h
+
+#include "mozilla/StaticPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsCoord.h"
+#include "nsCOMPtr.h"
+#include "nsFont.h"
+#include "nsAtom.h"
+#include "nsLanguageAtomService.h"
+
+namespace mozilla {
+
+struct LangGroupFontPrefs {
+ // Font sizes default to zero; they will be set in GetFontPreferences
+ LangGroupFontPrefs()
+ : mLangGroup(nullptr),
+ mMinimumFontSize({0}),
+ mDefaultVariableFont(StyleGenericFontFamily::Serif, {0}),
+ mDefaultSerifFont(StyleGenericFontFamily::Serif, {0}),
+ mDefaultSansSerifFont(StyleGenericFontFamily::SansSerif, {0}),
+ mDefaultMonospaceFont(StyleGenericFontFamily::Monospace, {0}),
+ mDefaultCursiveFont(StyleGenericFontFamily::Cursive, {0}),
+ mDefaultFantasyFont(StyleGenericFontFamily::Fantasy, {0}),
+ mDefaultSystemUiFont(StyleGenericFontFamily::SystemUi, {0}) {}
+
+ StyleGenericFontFamily GetDefaultGeneric() const {
+ return mDefaultVariableFont.family.families.list.AsSpan()[0].AsGeneric();
+ }
+
+ void Reset() {
+ // Throw away any other LangGroupFontPrefs objects:
+ mNext = nullptr;
+
+ // Make GetFontPreferences reinitialize mLangGroupFontPrefs:
+ mLangGroup = nullptr;
+ }
+
+ // Initialize this with the data for a given language
+ void Initialize(nsStaticAtom* aLangGroupAtom);
+
+ /**
+ * Get the default font for the given language and generic font ID.
+ * aLanguage may not be nullptr.
+ *
+ * This object is read-only, you must copy the font to modify it.
+ *
+ * For aFontID corresponding to a CSS Generic, the nsFont returned has
+ * its name set to that generic font's name, and its size set to
+ * the user's preference for font size for that generic and the
+ * given language.
+ */
+ const nsFont* GetDefaultFont(StyleGenericFontFamily aFamily) const {
+ switch (aFamily) {
+ // Special (our default variable width font and fixed width font)
+ case StyleGenericFontFamily::None:
+ return &mDefaultVariableFont;
+ // CSS
+ case StyleGenericFontFamily::Serif:
+ return &mDefaultSerifFont;
+ case StyleGenericFontFamily::SansSerif:
+ return &mDefaultSansSerifFont;
+ case StyleGenericFontFamily::Monospace:
+ return &mDefaultMonospaceFont;
+ case StyleGenericFontFamily::Cursive:
+ return &mDefaultCursiveFont;
+ case StyleGenericFontFamily::Fantasy:
+ return &mDefaultFantasyFont;
+ case StyleGenericFontFamily::SystemUi:
+ return &mDefaultSystemUiFont;
+ case StyleGenericFontFamily::MozEmoji:
+ // This shouldn't appear in font family names.
+ break;
+ }
+ MOZ_ASSERT_UNREACHABLE("invalid font id");
+ return nullptr;
+ }
+
+ nsStaticAtom* mLangGroup;
+ Length mMinimumFontSize;
+ nsFont mDefaultVariableFont;
+ nsFont mDefaultSerifFont;
+ nsFont mDefaultSansSerifFont;
+ nsFont mDefaultMonospaceFont;
+ nsFont mDefaultCursiveFont;
+ nsFont mDefaultFantasyFont;
+ nsFont mDefaultSystemUiFont;
+ UniquePtr<LangGroupFontPrefs> mNext;
+};
+
+/**
+ * Some functionality that has historically lived on nsPresContext does not
+ * actually need to be per-document. This singleton class serves as a host
+ * for that functionality. We delegate to it from nsPresContext where
+ * appropriate, and use it standalone in some cases as well.
+ */
+class StaticPresData {
+ public:
+ // Initialization and shutdown of the singleton. Called exactly once.
+ static void Init();
+ static void Shutdown();
+
+ // Gets an instance of the singleton. Infallible between the calls to Init
+ // and Shutdown.
+ static StaticPresData* Get();
+
+ /**
+ * Given a language, get the language group name, which can
+ * be used as an argument to LangGroupFontPrefs::Initialize()
+ *
+ * aNeedsToCache is used for two things. If null, it indicates that
+ * the nsLanguageAtomService is safe to cache the result of the
+ * language group lookup, either because we're on the main thread,
+ * or because we're on a style worker thread but the font lock has
+ * been acquired. If non-null, it indicates that it's not safe to
+ * cache the result of the language group lookup (because we're on
+ * a style worker thread without the lock acquired). In this case,
+ * GetLanguageGroup will store true in *aNeedsToCache true if we
+ * would have cached the result of a new lookup, and false if we
+ * were able to use an existing cached result. Thus, callers that
+ * get a true *aNeedsToCache outparam value should make an effort
+ * to re-call GetLanguageGroup when it is safe to cache, to avoid
+ * recomputing the language group again later.
+ */
+ nsStaticAtom* GetLangGroup(nsAtom* aLanguage,
+ bool* aNeedsToCache = nullptr) const;
+
+ /**
+ * Same as GetLangGroup, but will not cache the result
+ */
+ nsStaticAtom* GetUncachedLangGroup(nsAtom* aLanguage) const;
+
+ /**
+ * Fetch the user's font preferences for the given aLanguage's
+ * langugage group.
+ *
+ * The original code here is pretty old, and includes an optimization
+ * whereby language-specific prefs are read per-document, and the
+ * results are stored in a linked list, which is assumed to be very short
+ * since most documents only ever use one language.
+ *
+ * Storing this per-session rather than per-document would almost certainly
+ * be fine. But just to be on the safe side, we leave the old mechanism as-is,
+ * with an additional per-session cache that new callers can use if they don't
+ * have a PresContext.
+ *
+ * See comment on GetLangGroup for the usage of aNeedsToCache.
+ */
+ const LangGroupFontPrefs* GetFontPrefsForLang(nsAtom* aLanguage,
+ bool* aNeedsToCache = nullptr);
+ const nsFont* GetDefaultFont(uint8_t aFontID, nsAtom* aLanguage,
+ const LangGroupFontPrefs* aPrefs) const;
+
+ void InvalidateFontPrefs() { mLangGroupFontPrefs.Reset(); }
+
+ private:
+ // Private constructor/destructor, to prevent other code from inadvertently
+ // instantiating or deleting us. (Though we need to declare StaticAutoPtr as
+ // a friend to give it permission.)
+ StaticPresData();
+ ~StaticPresData() = default;
+ friend class StaticAutoPtr<StaticPresData>;
+
+ nsLanguageAtomService* mLangService;
+ LangGroupFontPrefs mLangGroupFontPrefs;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_StaticPresData_h
diff --git a/layout/base/SurfaceFromElementResult.h b/layout/base/SurfaceFromElementResult.h
new file mode 100644
index 0000000000..4b889609e6
--- /dev/null
+++ b/layout/base/SurfaceFromElementResult.h
@@ -0,0 +1,104 @@
+/* -*- 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_SurfaceFromElementResult_h
+#define mozilla_SurfaceFromElementResult_h
+
+#include "ImageContainer.h"
+#include "gfxTypes.h"
+#include "mozilla/gfx/Point.h"
+#include "nsCOMPtr.h"
+#include <cstdint>
+
+class imgIContainer;
+class imgIRequest;
+class nsIPrincipal;
+class nsLayoutUtils;
+
+namespace mozilla {
+
+namespace dom {
+class CanvasRenderingContext2D;
+class ImageBitmap;
+} // namespace dom
+
+namespace gfx {
+class SourceSurface;
+}
+
+struct DirectDrawInfo {
+ /* imgIContainer to directly draw to a context */
+ nsCOMPtr<imgIContainer> mImgContainer;
+ /* which frame to draw */
+ uint32_t mWhichFrame;
+ /* imgIContainer flags to use when drawing */
+ uint32_t mDrawingFlags;
+};
+
+struct SurfaceFromElementResult {
+ friend class mozilla::dom::CanvasRenderingContext2D;
+ friend class mozilla::dom::ImageBitmap;
+ friend class ::nsLayoutUtils;
+
+ /* If SFEResult contains a valid surface, it either mLayersImage or
+ * mSourceSurface will be non-null, and GetSourceSurface() will not be null.
+ *
+ * For valid surfaces, mSourceSurface may be null if mLayersImage is
+ * non-null, but GetSourceSurface() will create mSourceSurface from
+ * mLayersImage when called.
+ */
+
+ /* Video elements (at least) often are already decoded as layers::Images. */
+ RefPtr<mozilla::layers::Image> mLayersImage;
+
+ protected:
+ /* GetSourceSurface() fills this and returns its non-null value if this
+ * SFEResult was successful. */
+ RefPtr<mozilla::gfx::SourceSurface> mSourceSurface;
+
+ public:
+ /* Contains info for drawing when there is no mSourceSurface. */
+ DirectDrawInfo mDrawInfo;
+
+ /* The size of the surface */
+ mozilla::gfx::IntSize mSize;
+ /* The size the surface is intended to be rendered at */
+ mozilla::gfx::IntSize mIntrinsicSize;
+ /* The crop rect of the surface, indicating what subset is valid. This will
+ * always be Nothing() unless SFE_ALLOW_UNCROPPED is set. */
+ mozilla::Maybe<mozilla::gfx::IntRect> mCropRect;
+ /* The principal associated with the element whose surface was returned.
+ If there is a surface, this will never be null. */
+ nsCOMPtr<nsIPrincipal> mPrincipal;
+ /* The image request, if the element is an nsIImageLoadingContent */
+ nsCOMPtr<imgIRequest> mImageRequest;
+ /* True if cross-origins redirects have been done in order to load this
+ * resource */
+ bool mHadCrossOriginRedirects;
+ /* Whether the element was "write only", that is, the bits should not be
+ * exposed to content */
+ bool mIsWriteOnly;
+ /* Whether the element was still loading. Some consumers need to handle
+ this case specially. */
+ bool mIsStillLoading;
+ /* Whether the element has a valid size. */
+ bool mHasSize;
+ /* Whether the element used CORS when loading. */
+ bool mCORSUsed;
+
+ gfxAlphaType mAlphaType;
+
+ // Methods:
+
+ SurfaceFromElementResult();
+
+ // Gets mSourceSurface, or makes a SourceSurface from mLayersImage.
+ const RefPtr<mozilla::gfx::SourceSurface>& GetSourceSurface();
+};
+
+} // namespace mozilla
+
+#endif // mozilla_SurfaceFromElementResult_h
diff --git a/layout/base/TouchManager.cpp b/layout/base/TouchManager.cpp
new file mode 100644
index 0000000000..0c34567b65
--- /dev/null
+++ b/layout/base/TouchManager.cpp
@@ -0,0 +1,476 @@
+/* -*- 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 "TouchManager.h"
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/EventTarget.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsView.h"
+#include "PositionedEventTargeting.h"
+
+using namespace mozilla::dom;
+
+namespace mozilla {
+
+StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>>
+ TouchManager::sCaptureTouchList;
+layers::LayersId TouchManager::sCaptureTouchLayersId;
+
+/*static*/
+void TouchManager::InitializeStatics() {
+ NS_ASSERTION(!sCaptureTouchList, "InitializeStatics called multiple times!");
+ sCaptureTouchList = new nsTHashMap<nsUint32HashKey, TouchManager::TouchInfo>;
+ sCaptureTouchLayersId = layers::LayersId{0};
+}
+
+/*static*/
+void TouchManager::ReleaseStatics() {
+ NS_ASSERTION(sCaptureTouchList, "ReleaseStatics called without Initialize!");
+ sCaptureTouchList = nullptr;
+}
+
+void TouchManager::Init(PresShell* aPresShell, Document* aDocument) {
+ mPresShell = aPresShell;
+ mDocument = aDocument;
+}
+
+void TouchManager::Destroy() {
+ EvictTouches(mDocument);
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+static nsIContent* GetNonAnonymousAncestor(EventTarget* aTarget) {
+ nsIContent* content = nsIContent::FromEventTargetOrNull(aTarget);
+ if (content && content->IsInNativeAnonymousSubtree()) {
+ content = content->FindFirstNonChromeOnlyAccessContent();
+ }
+ return content;
+}
+
+/*static*/
+void TouchManager::EvictTouchPoint(RefPtr<Touch>& aTouch,
+ Document* aLimitToDocument) {
+ nsCOMPtr<nsINode> node(
+ nsINode::FromEventTargetOrNull(aTouch->mOriginalTarget));
+ if (node) {
+ Document* doc = node->GetComposedDoc();
+ if (doc && (!aLimitToDocument || aLimitToDocument == doc)) {
+ PresShell* presShell = doc->GetPresShell();
+ if (presShell) {
+ nsIFrame* frame = presShell->GetRootFrame();
+ if (frame) {
+ nsCOMPtr<nsIWidget> widget =
+ frame->GetView()->GetNearestWidget(nullptr);
+ if (widget) {
+ WidgetTouchEvent event(true, eTouchEnd, widget);
+ event.mTouches.AppendElement(aTouch);
+ nsEventStatus status;
+ widget->DispatchEvent(&event, status);
+ }
+ }
+ }
+ }
+ }
+ if (!node || !aLimitToDocument || node->OwnerDoc() == aLimitToDocument) {
+ sCaptureTouchList->Remove(aTouch->Identifier());
+ }
+}
+
+/*static*/
+void TouchManager::AppendToTouchList(
+ WidgetTouchEvent::TouchArrayBase* aTouchList) {
+ for (const auto& data : sCaptureTouchList->Values()) {
+ const RefPtr<Touch>& touch = data.mTouch;
+ touch->mChanged = false;
+ aTouchList->AppendElement(touch);
+ }
+}
+
+void TouchManager::EvictTouches(Document* aLimitToDocument) {
+ WidgetTouchEvent::AutoTouchArray touches;
+ AppendToTouchList(&touches);
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ EvictTouchPoint(touches[i], aLimitToDocument);
+ }
+ sCaptureTouchLayersId = layers::LayersId{0};
+}
+
+/* static */
+nsIFrame* TouchManager::SetupTarget(WidgetTouchEvent* aEvent,
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(aEvent);
+
+ if (!aEvent || aEvent->mMessage != eTouchStart) {
+ // All touch events except for touchstart use a captured target.
+ return aFrame;
+ }
+
+ nsIFrame* target = aFrame;
+ for (int32_t i = aEvent->mTouches.Length(); i;) {
+ --i;
+ dom::Touch* touch = aEvent->mTouches[i];
+
+ int32_t id = touch->Identifier();
+ if (!TouchManager::HasCapturedTouch(id)) {
+ // find the target for this touch
+ RelativeTo relativeTo{aFrame};
+ nsPoint eventPoint = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ aEvent, touch->mRefPoint, relativeTo);
+ target = FindFrameTargetedByInputEvent(aEvent, relativeTo, eventPoint);
+ if (target) {
+ nsCOMPtr<nsIContent> targetContent;
+ target->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ touch->SetTouchTarget(targetContent
+ ? targetContent->GetAsElementOrParentElement()
+ : nullptr);
+ } else {
+ aEvent->mTouches.RemoveElementAt(i);
+ }
+ } else {
+ // This touch is an old touch, we need to ensure that is not
+ // marked as changed and set its target correctly
+ touch->mChanged = false;
+ RefPtr<dom::Touch> oldTouch = TouchManager::GetCapturedTouch(id);
+ if (oldTouch) {
+ touch->SetTouchTarget(oldTouch->mOriginalTarget);
+ }
+ }
+ }
+ return target;
+}
+
+/* static */
+nsIFrame* TouchManager::SuppressInvalidPointsAndGetTargetedFrame(
+ WidgetTouchEvent* aEvent) {
+ MOZ_ASSERT(aEvent);
+
+ if (!aEvent || aEvent->mMessage != eTouchStart) {
+ // All touch events except for touchstart use a captured target.
+ return nullptr;
+ }
+
+ // if this is a continuing session, ensure that all these events are
+ // in the same document by taking the target of the events already in
+ // the capture list
+ nsCOMPtr<nsIContent> anyTarget;
+ if (aEvent->mTouches.Length() > 1) {
+ anyTarget = TouchManager::GetAnyCapturedTouchTarget();
+ }
+
+ nsIFrame* frame = nullptr;
+ for (int32_t i = aEvent->mTouches.Length(); i;) {
+ --i;
+ dom::Touch* touch = aEvent->mTouches[i];
+ if (TouchManager::HasCapturedTouch(touch->Identifier())) {
+ continue;
+ }
+
+ MOZ_ASSERT(touch->mOriginalTarget);
+ nsCOMPtr<nsIContent> targetContent = do_QueryInterface(touch->GetTarget());
+ nsIFrame* targetFrame =
+ targetContent ? targetContent->GetPrimaryFrame() : nullptr;
+ if (targetFrame && !anyTarget) {
+ anyTarget = targetContent;
+ } else {
+ nsIFrame* newTargetFrame = nullptr;
+ for (nsIFrame* f = targetFrame; f;
+ f = nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(f)) {
+ if (f->PresContext()->Document() == anyTarget->OwnerDoc()) {
+ newTargetFrame = f;
+ break;
+ }
+ // We must be in a subdocument so jump directly to the root frame.
+ // GetParentOrPlaceholderForCrossDoc gets called immediately to
+ // jump up to the containing document.
+ f = f->PresShell()->GetRootFrame();
+ }
+ // if we couldn't find a target frame in the same document as
+ // anyTarget, remove the touch from the capture touch list, as
+ // well as the event->mTouches array. touchmove events that aren't
+ // in the captured touch list will be discarded
+ if (!newTargetFrame) {
+ touch->mIsTouchEventSuppressed = true;
+ } else {
+ targetFrame = newTargetFrame;
+ targetFrame->GetContentForEvent(aEvent, getter_AddRefs(targetContent));
+ touch->SetTouchTarget(targetContent
+ ? targetContent->GetAsElementOrParentElement()
+ : nullptr);
+ }
+ }
+ if (targetFrame) {
+ frame = targetFrame;
+ }
+ }
+ return frame;
+}
+
+bool TouchManager::PreHandleEvent(WidgetEvent* aEvent, nsEventStatus* aStatus,
+ bool& aTouchIsNew,
+ nsCOMPtr<nsIContent>& aCurrentEventContent) {
+ MOZ_DIAGNOSTIC_ASSERT(aEvent->IsTrusted());
+
+ // NOTE: If you need to handle new event messages here, you need to add new
+ // cases in PresShell::EventHandler::PrepareToDispatchEvent().
+ switch (aEvent->mMessage) {
+ case eTouchStart: {
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ // if there is only one touch in this touchstart event, assume that it is
+ // the start of a new touch session and evict any old touches in the
+ // queue
+ if (touchEvent->mTouches.Length() == 1) {
+ EvictTouches();
+ // Per
+ // https://w3c.github.io/touch-events/#touchevent-implementer-s-note,
+ // all touch event should be dispatched to the same document that first
+ // touch event associated to. We cache layers id of the first touchstart
+ // event, all subsequent touch events will use the same layers id.
+ sCaptureTouchLayersId = aEvent->mLayersId;
+ } else {
+ touchEvent->mLayersId = sCaptureTouchLayersId;
+ }
+ // Add any new touches to the queue
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ for (int32_t i = touches.Length(); i;) {
+ --i;
+ Touch* touch = touches[i];
+ int32_t id = touch->Identifier();
+ if (!sCaptureTouchList->Get(id, nullptr)) {
+ // If it is not already in the queue, it is a new touch
+ touch->mChanged = true;
+ }
+ touch->mMessage = aEvent->mMessage;
+ TouchInfo info = {
+ touch, GetNonAnonymousAncestor(touch->mOriginalTarget), true};
+ sCaptureTouchList->InsertOrUpdate(id, info);
+ if (touch->mIsTouchEventSuppressed) {
+ // We're going to dispatch touch event. Remove this touch instance if
+ // it is suppressed.
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ }
+ break;
+ }
+ case eTouchMove: {
+ // Check for touches that changed. Mark them add to queue
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ touchEvent->mLayersId = sCaptureTouchLayersId;
+ bool haveChanged = false;
+ for (int32_t i = touches.Length(); i;) {
+ --i;
+ Touch* touch = touches[i];
+ if (!touch) {
+ continue;
+ }
+ int32_t id = touch->Identifier();
+ touch->mMessage = aEvent->mMessage;
+
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(id, &info)) {
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ const RefPtr<Touch> oldTouch = info.mTouch;
+ if (!oldTouch->Equals(touch)) {
+ touch->mChanged = true;
+ haveChanged = true;
+ }
+
+ nsCOMPtr<EventTarget> targetPtr = oldTouch->mOriginalTarget;
+ if (!targetPtr) {
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
+ if (!targetNode->IsInComposedDoc()) {
+ targetPtr = info.mNonAnonymousTarget;
+ }
+ touch->SetTouchTarget(targetPtr);
+
+ info.mTouch = touch;
+ // info.mNonAnonymousTarget is still valid from above
+ sCaptureTouchList->InsertOrUpdate(id, info);
+ // if we're moving from touchstart to touchmove for this touch
+ // we allow preventDefault to prevent mouse events
+ if (oldTouch->mMessage != touch->mMessage) {
+ aTouchIsNew = true;
+ }
+ if (oldTouch->mIsTouchEventSuppressed) {
+ touch->mIsTouchEventSuppressed = true;
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ }
+ // is nothing has changed, we should just return
+ if (!haveChanged) {
+ if (aTouchIsNew) {
+ // however, if this is the first touchmove after a touchstart,
+ // it is special in that preventDefault is allowed on it, so
+ // we must dispatch it to content even if nothing changed. we
+ // arbitrarily pick the first touch point to be the "changed"
+ // touch because firing an event with no changed events doesn't
+ // work.
+ for (uint32_t i = 0; i < touchEvent->mTouches.Length(); ++i) {
+ if (touchEvent->mTouches[i]) {
+ touchEvent->mTouches[i]->mChanged = true;
+ break;
+ }
+ }
+ } else {
+ // This touch event isn't going to be dispatched on the main-thread,
+ // we need to tell it to APZ because returned nsEventStatus is
+ // unreliable to tell whether the event was preventDefaulted or not.
+ layers::InputAPZContext::SetDropped();
+ return false;
+ }
+ }
+ break;
+ }
+ case eTouchEnd:
+ case eTouchCancel: {
+ // Remove the changed touches
+ // need to make sure we only remove touches that are ending here
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ touchEvent->mLayersId = sCaptureTouchLayersId;
+ for (int32_t i = touches.Length(); i;) {
+ --i;
+ Touch* touch = touches[i];
+ if (!touch) {
+ continue;
+ }
+ touch->mMessage = aEvent->mMessage;
+ touch->mChanged = true;
+
+ int32_t id = touch->Identifier();
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(id, &info)) {
+ continue;
+ }
+ nsCOMPtr<EventTarget> targetPtr = info.mTouch->mOriginalTarget;
+ nsCOMPtr<nsINode> targetNode(do_QueryInterface(targetPtr));
+ if (targetNode && !targetNode->IsInComposedDoc()) {
+ targetPtr = info.mNonAnonymousTarget;
+ }
+
+ aCurrentEventContent = do_QueryInterface(targetPtr);
+ touch->SetTouchTarget(targetPtr);
+ sCaptureTouchList->Remove(id);
+ if (info.mTouch->mIsTouchEventSuppressed) {
+ touches.RemoveElementAt(i);
+ continue;
+ }
+ }
+ // add any touches left in the touch list, but ensure changed=false
+ AppendToTouchList(&touches);
+ break;
+ }
+ case eTouchPointerCancel: {
+ // Don't generate pointer events by touch events after eTouchPointerCancel
+ // is received.
+ WidgetTouchEvent* touchEvent = aEvent->AsTouchEvent();
+ WidgetTouchEvent::TouchArray& touches = touchEvent->mTouches;
+ touchEvent->mLayersId = sCaptureTouchLayersId;
+ for (uint32_t i = 0; i < touches.Length(); ++i) {
+ Touch* touch = touches[i];
+ if (!touch) {
+ continue;
+ }
+ int32_t id = touch->Identifier();
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(id, &info)) {
+ continue;
+ }
+ info.mConvertToPointer = false;
+ sCaptureTouchList->InsertOrUpdate(id, info);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ return true;
+}
+
+/*static*/
+already_AddRefed<nsIContent> TouchManager::GetAnyCapturedTouchTarget() {
+ nsCOMPtr<nsIContent> result = nullptr;
+ if (sCaptureTouchList->Count() == 0) {
+ return result.forget();
+ }
+ for (const auto& data : sCaptureTouchList->Values()) {
+ const RefPtr<Touch>& touch = data.mTouch;
+ if (touch) {
+ EventTarget* target = touch->GetTarget();
+ if (target) {
+ result = nsIContent::FromEventTargetOrNull(target);
+ break;
+ }
+ }
+ }
+ return result.forget();
+}
+
+/*static*/
+bool TouchManager::HasCapturedTouch(int32_t aId) {
+ return sCaptureTouchList->Contains(aId);
+}
+
+/*static*/
+already_AddRefed<Touch> TouchManager::GetCapturedTouch(int32_t aId) {
+ RefPtr<Touch> touch;
+ TouchInfo info;
+ if (sCaptureTouchList->Get(aId, &info)) {
+ touch = info.mTouch;
+ }
+ return touch.forget();
+}
+
+/*static*/
+bool TouchManager::ShouldConvertTouchToPointer(const Touch* aTouch,
+ const WidgetTouchEvent* aEvent) {
+ if (!aTouch || !aTouch->convertToPointer) {
+ return false;
+ }
+ TouchInfo info;
+ if (!sCaptureTouchList->Get(aTouch->Identifier(), &info)) {
+ // This check runs before the TouchManager has the touch registered in its
+ // touch list. It's because we dispatching pointer events before handling
+ // touch events. So we convert eTouchStart to pointerdown even it's not
+ // registered.
+ // Check WidgetTouchEvent::mMessage because Touch::mMessage is assigned when
+ // pre-handling touch events.
+ return aEvent->mMessage == eTouchStart;
+ }
+
+ if (!info.mConvertToPointer) {
+ return false;
+ }
+
+ switch (aEvent->mMessage) {
+ case eTouchStart: {
+ // We don't want to fire duplicated pointerdown.
+ return false;
+ }
+ case eTouchMove: {
+ return !aTouch->Equals(info.mTouch);
+ }
+ default:
+ break;
+ }
+ return true;
+}
+
+} // namespace mozilla
diff --git a/layout/base/TouchManager.h b/layout/base/TouchManager.h
new file mode 100644
index 0000000000..0e12c25c7d
--- /dev/null
+++ b/layout/base/TouchManager.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/.
+ */
+
+/* Description of TouchManager class.
+ * Incapsulate code related with work of touch events.
+ */
+
+#ifndef TouchManager_h_
+#define TouchManager_h_
+
+#include "mozilla/BasicEvents.h"
+#include "mozilla/dom/Touch.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TouchEvents.h"
+#include "nsRefPtrHashtable.h"
+
+namespace mozilla {
+class PresShell;
+
+class TouchManager {
+ public:
+ // Initialize and release static variables
+ static void InitializeStatics();
+ static void ReleaseStatics();
+
+ void Init(PresShell* aPresShell, dom::Document* aDocument);
+ void Destroy();
+
+ // Perform hit test and setup the event targets for touchstart. Other touch
+ // events are dispatched to the same target as touchstart.
+ static nsIFrame* SetupTarget(WidgetTouchEvent* aEvent, nsIFrame* aFrame);
+
+ /**
+ * This function checks whether all touch points hit elements in the same
+ * document. If not, we try to find its cross document parent which is in the
+ * same document of the existing target as the event target. We mark the
+ * touch point as suppressed if can't find it. The suppressed touch points are
+ * removed in TouchManager::PreHandleEvent so that we don't dispatch them to
+ * content.
+ *
+ * @param aEvent A touch event to be checked.
+ *
+ * @return The targeted frame of aEvent.
+ */
+ static nsIFrame* SuppressInvalidPointsAndGetTargetedFrame(
+ WidgetTouchEvent* aEvent);
+
+ bool PreHandleEvent(mozilla::WidgetEvent* aEvent, nsEventStatus* aStatus,
+ bool& aTouchIsNew,
+ nsCOMPtr<nsIContent>& aCurrentEventContent);
+
+ static already_AddRefed<nsIContent> GetAnyCapturedTouchTarget();
+ static bool HasCapturedTouch(int32_t aId);
+ static already_AddRefed<dom::Touch> GetCapturedTouch(int32_t aId);
+ static bool ShouldConvertTouchToPointer(const dom::Touch* aTouch,
+ const WidgetTouchEvent* aEvent);
+
+ private:
+ void EvictTouches(dom::Document* aLimitToDocument = nullptr);
+ static void EvictTouchPoint(RefPtr<dom::Touch>& aTouch,
+ dom::Document* aLimitToDocument);
+ static void AppendToTouchList(WidgetTouchEvent::TouchArrayBase* aTouchList);
+
+ RefPtr<PresShell> mPresShell;
+ RefPtr<dom::Document> mDocument;
+
+ struct TouchInfo {
+ RefPtr<mozilla::dom::Touch> mTouch;
+ nsCOMPtr<nsIContent> mNonAnonymousTarget;
+ bool mConvertToPointer;
+ };
+
+ static StaticAutoPtr<nsTHashMap<nsUint32HashKey, TouchInfo>>
+ sCaptureTouchList;
+ static layers::LayersId sCaptureTouchLayersId;
+};
+
+} // namespace mozilla
+
+#endif /* !defined(TouchManager_h_) */
diff --git a/layout/base/UnitTransforms.h b/layout/base/UnitTransforms.h
new file mode 100644
index 0000000000..7bbd0f088f
--- /dev/null
+++ b/layout/base/UnitTransforms.h
@@ -0,0 +1,388 @@
+/* -*- 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 MOZ_UNIT_TRANSFORMS_H_
+#define MOZ_UNIT_TRANSFORMS_H_
+
+#include "Units.h"
+#include "mozilla/gfx/Matrix.h"
+#include "mozilla/Maybe.h"
+#include "nsRegion.h"
+
+namespace mozilla {
+
+// Convenience functions for converting an entity from one strongly-typed
+// coordinate system to another without changing the values it stores (this
+// can be thought of as a cast).
+// To use these functions, you must provide a justification for each use!
+// Feel free to add more justifications to PixelCastJustification, along with
+// a comment that explains under what circumstances it is appropriate to use.
+
+enum class PixelCastJustification : uint8_t {
+ // For the root layer, Screen Pixel = Parent Layer Pixel.
+ ScreenIsParentLayerForRoot,
+ // On the layout side, Screen Pixel = LayoutDevice at the outer-window level.
+ LayoutDeviceIsScreenForBounds,
+ // For the root layer, Render Target Pixel = Parent Layer Pixel.
+ RenderTargetIsParentLayerForRoot,
+ // For the root composition size we want to view it as layer pixels in any
+ // layer
+ ParentLayerToLayerForRootComposition,
+ // The Layer coordinate space for one layer is the ParentLayer coordinate
+ // space for its children
+ MovingDownToChildren,
+ // The transform that is usually used to convert between two coordinate
+ // systems is not available (for example, because the object that stores it
+ // is being destroyed), so fall back to the identity.
+ TransformNotAvailable,
+ // When an OS event is initially constructed, its reference point is
+ // technically in screen pixels, as it has not yet accounted for any
+ // asynchronous transforms. This justification is for viewing the initial
+ // reference point as a screen point. The reverse is useful when synthetically
+ // created WidgetEvents need to be converted back to InputData.
+ LayoutDeviceIsScreenForUntransformedEvent,
+ // Similar to LayoutDeviceIsScreenForUntransformedEvent, PBrowser handles
+ // some widget/tab dimension information as the OS does -- in screen units.
+ LayoutDeviceIsScreenForTabDims,
+ // A combination of LayoutDeviceIsScreenForBounds and
+ // ScreenIsParentLayerForRoot, which is how we're using it.
+ LayoutDeviceIsParentLayerForRCDRSF,
+ // Used to treat the product of AsyncTransformComponentMatrix objects
+ // as an AsyncTransformMatrix. See the definitions of these matrices in
+ // LayersTypes.h for details.
+ MultipleAsyncTransforms,
+ // We have reason to believe a layer doesn't have a local transform.
+ // Should only be used if we've already checked or asserted this.
+ NoTransformOnLayer,
+ // LayerPixels are ImagePixels
+ LayerIsImage,
+ // External pixels are the same scale as screen pixels
+ ExternalIsScreen,
+ // LayerToScreenMatrix is used as LayoutDeviceToLayoutDevice, because
+ // out-of-process iframes uses LayoutDevicePixels as the type system-visible
+ // type of their top-level event coordinate space even if technically
+ // inaccurate.
+ ContentProcessIsLayerInUiProcess,
+ // Propagating TransformToAncestorScale to a child process.
+ PropagatingToChildProcess,
+ // A quantity represents a proportion of a page length, e.g. "0.5 pages".
+ // The proportion does not need to be scaled when converting between
+ // units (the page length that it's mutlipled by will be scaled instead).
+ DeltaIsPageProportion,
+ // Used to cast between CSS and OuterCSS pixels when moving between code
+ // that deals with content outside a scroll frame generically (which would
+ // use CSS pixels) and code related to the scroll frame in APZ (which wants
+ // such quantities in OuterCSS pixels).
+ CSSPixelsOfSurroundingContent,
+};
+
+template <class TargetUnits, class SourceUnits>
+gfx::CoordTyped<TargetUnits> ViewAs(const gfx::CoordTyped<SourceUnits>& aCoord,
+ PixelCastJustification) {
+ return gfx::CoordTyped<TargetUnits>(aCoord.value);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntCoordTyped<TargetUnits> ViewAs(
+ const gfx::IntCoordTyped<SourceUnits>& aCoord, PixelCastJustification) {
+ return gfx::IntCoordTyped<TargetUnits>(aCoord.value);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::SizeTyped<TargetUnits> ViewAs(const gfx::SizeTyped<SourceUnits>& aSize,
+ PixelCastJustification) {
+ return gfx::SizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntSizeTyped<TargetUnits> ViewAs(
+ const gfx::IntSizeTyped<SourceUnits>& aSize, PixelCastJustification) {
+ return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfx::PointTyped<SourceUnits>& aPoint,
+ PixelCastJustification) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntPointTyped<TargetUnits> ViewAs(
+ const gfx::IntPointTyped<SourceUnits>& aPoint, PixelCastJustification) {
+ return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::RectTyped<TargetUnits> ViewAs(const gfx::RectTyped<SourceUnits>& aRect,
+ PixelCastJustification) {
+ return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.Width(),
+ aRect.Height());
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntRectTyped<TargetUnits> ViewAs(
+ const gfx::IntRectTyped<SourceUnits>& aRect, PixelCastJustification) {
+ return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.Width(),
+ aRect.Height());
+}
+template <class TargetUnits, class SourceUnits>
+gfx::MarginTyped<TargetUnits> ViewAs(
+ const gfx::MarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
+ return gfx::MarginTyped<TargetUnits>(aMargin.top.value, aMargin.right.value,
+ aMargin.bottom.value,
+ aMargin.left.value);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntMarginTyped<TargetUnits> ViewAs(
+ const gfx::IntMarginTyped<SourceUnits>& aMargin, PixelCastJustification) {
+ return gfx::IntMarginTyped<TargetUnits>(aMargin.top, aMargin.right,
+ aMargin.bottom, aMargin.left);
+}
+template <class TargetUnits, class SourceUnits>
+gfx::IntRegionTyped<TargetUnits> ViewAs(
+ const gfx::IntRegionTyped<SourceUnits>& aRegion, PixelCastJustification) {
+ return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(
+ aRegion.ToUnknownRegion());
+}
+template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
+gfx::ScaleFactor<SourceUnits, NewTargetUnits> ViewTargetAs(
+ const gfx::ScaleFactor<SourceUnits, OldTargetUnits>& aScaleFactor,
+ PixelCastJustification) {
+ return gfx::ScaleFactor<SourceUnits, NewTargetUnits>(aScaleFactor.scale);
+}
+template <class NewTargetUnits, class OldTargetUnits, class SourceUnits>
+gfx::ScaleFactors2D<SourceUnits, NewTargetUnits> ViewTargetAs(
+ const gfx::ScaleFactors2D<SourceUnits, OldTargetUnits>& aScaleFactors,
+ PixelCastJustification) {
+ return gfx::ScaleFactors2D<SourceUnits, NewTargetUnits>(aScaleFactors.xScale,
+ aScaleFactors.yScale);
+}
+template <class TargetUnits, class SourceUnits>
+Maybe<gfx::IntRectTyped<TargetUnits>> ViewAs(
+ const Maybe<gfx::IntRectTyped<SourceUnits>>& aRect,
+ PixelCastJustification aJustification) {
+ if (aRect.isSome()) {
+ return Some(ViewAs<TargetUnits>(aRect.value(), aJustification));
+ }
+ return Nothing();
+}
+// Unlike the other functions in this category, these functions take the
+// target matrix or scale type, rather than its source and target unit types, as
+// the explicit template argument, so an example invocation is:
+// ViewAs<ScreenToLayerMatrix4x4>(otherTypedMatrix, justification)
+// The reason is that if it took the source and target unit types as two
+// template arguments, there may be some confusion as to which is the
+// source and which is the target.
+template <class TargetMatrix, class SourceMatrixSourceUnits,
+ class SourceMatrixTargetUnits>
+TargetMatrix ViewAs(const gfx::Matrix4x4Typed<SourceMatrixSourceUnits,
+ SourceMatrixTargetUnits>& aMatrix,
+ PixelCastJustification) {
+ return aMatrix.template Cast<TargetMatrix>();
+}
+template <class TargetMatrix, class SourceMatrixSourceUnits,
+ class SourceMatrixTargetUnits>
+Maybe<TargetMatrix> ViewAs(
+ const Maybe<gfx::Matrix4x4Typed<SourceMatrixSourceUnits,
+ SourceMatrixTargetUnits>>& aMatrix,
+ PixelCastJustification) {
+ if (aMatrix.isSome()) {
+ return Some(aMatrix->template Cast<TargetMatrix>());
+ }
+ return Nothing();
+}
+template <class TargetScale, class SourceScaleSourceUnits,
+ class SourceScaleTargetUnits>
+TargetScale ViewAs(const gfx::ScaleFactor<SourceScaleSourceUnits,
+ SourceScaleTargetUnits>& aScale,
+ PixelCastJustification) {
+ return TargetScale{aScale.scale};
+}
+
+// A non-member overload of ToUnknownMatrix() for use on a Maybe<Matrix>.
+// We can't make this a member because we can't inject a member into Maybe.
+template <typename SourceUnits, typename TargetUnits>
+Maybe<gfx::Matrix4x4> ToUnknownMatrix(
+ const Maybe<gfx::Matrix4x4Typed<SourceUnits, TargetUnits>>& aMatrix) {
+ if (aMatrix.isSome()) {
+ return Some(aMatrix->ToUnknownMatrix());
+ }
+ return Nothing();
+}
+
+// Convenience functions for casting untyped entities to typed entities.
+// Using these functions does not require a justification, but once we convert
+// all code to use strongly typed units they should not be needed any longer.
+template <class TargetUnits>
+gfx::CoordTyped<TargetUnits> ViewAs(const gfx::Coord& aCoord) {
+ return gfx::CoordTyped<TargetUnits>(aCoord.value);
+}
+template <class TargetUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfxPoint& aPoint) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::PointTyped<TargetUnits> ViewAs(const gfx::Point& aPoint) {
+ return gfx::PointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::RectTyped<TargetUnits> ViewAs(const gfx::Rect& aRect) {
+ return gfx::RectTyped<TargetUnits>(aRect.x, aRect.y, aRect.Width(),
+ aRect.Height());
+}
+template <class TargetUnits>
+gfx::IntSizeTyped<TargetUnits> ViewAs(const nsIntSize& aSize) {
+ return gfx::IntSizeTyped<TargetUnits>(aSize.width, aSize.height);
+}
+template <class TargetUnits>
+gfx::IntPointTyped<TargetUnits> ViewAs(const nsIntPoint& aPoint) {
+ return gfx::IntPointTyped<TargetUnits>(aPoint.x, aPoint.y);
+}
+template <class TargetUnits>
+gfx::IntRectTyped<TargetUnits> ViewAs(const nsIntRect& aRect) {
+ return gfx::IntRectTyped<TargetUnits>(aRect.x, aRect.y, aRect.Width(),
+ aRect.Height());
+}
+template <class TargetUnits>
+gfx::IntRegionTyped<TargetUnits> ViewAs(const nsIntRegion& aRegion) {
+ return gfx::IntRegionTyped<TargetUnits>::FromUnknownRegion(aRegion);
+}
+// Unlike the other functions in this category, these functions take the
+// target matrix or scale type, rather than its source and target unit
+// types, as the template argument, so an example invocation is:
+// ViewAs<ScreenToLayerMatrix4x4>(untypedMatrix)
+// The reason is that if it took the source and target unit types as two
+// template arguments, there may be some confusion as to which is the
+// source and which is the target.
+template <class TypedScale>
+TypedScale ViewAs(const Scale2D& aScale) {
+ return TypedScale(aScale.xScale, aScale.yScale);
+}
+template <class TypedMatrix>
+TypedMatrix ViewAs(const gfx::Matrix4x4& aMatrix) {
+ return TypedMatrix::FromUnknownMatrix(aMatrix);
+}
+
+// Convenience functions for transforming an entity from one strongly-typed
+// coordinate system to another using the provided transformation matrix.
+template <typename TargetUnits, typename SourceUnits>
+static gfx::PointTyped<TargetUnits> TransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aPoint) {
+ return aTransform.TransformPoint(aPoint);
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntPointTyped<TargetUnits> TransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntPointTyped<SourceUnits>& aPoint) {
+ return RoundedToInt(
+ TransformBy(aTransform, gfx::PointTyped<SourceUnits>(aPoint)));
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::RectTyped<TargetUnits> TransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::RectTyped<SourceUnits>& aRect) {
+ return aTransform.TransformBounds(aRect);
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntRectTyped<TargetUnits> TransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRectTyped<SourceUnits>& aRect) {
+ return RoundedToInt(
+ TransformBy(aTransform, gfx::RectTyped<SourceUnits>(aRect)));
+}
+template <typename TargetUnits, typename SourceUnits>
+static gfx::IntRegionTyped<TargetUnits> TransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRegionTyped<SourceUnits>& aRegion) {
+ return ViewAs<TargetUnits>(
+ aRegion.ToUnknownRegion().Transform(aTransform.ToUnknownMatrix()));
+}
+
+// Transform |aVector|, which is anchored at |aAnchor|, by the given transform
+// matrix, yielding a point in |TargetUnits|.
+// The anchor is necessary because with 3D tranforms, the location of the
+// vector can affect the result of the transform.
+template <typename TargetUnits, typename SourceUnits>
+static gfx::PointTyped<TargetUnits> TransformVector(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aVector,
+ const gfx::PointTyped<SourceUnits>& aAnchor) {
+ gfx::PointTyped<TargetUnits> transformedStart =
+ TransformBy(aTransform, aAnchor);
+ gfx::PointTyped<TargetUnits> transformedEnd =
+ TransformBy(aTransform, aAnchor + aVector);
+ return transformedEnd - transformedStart;
+}
+
+// UntransformBy() and UntransformVector() are like TransformBy() and
+// TransformVector(), respectively, but are intended for cases where
+// the transformation matrix is the inverse of a 3D projection. When
+// using such transforms, the resulting Point4D is only meaningful
+// if it has a positive w-coordinate. To handle this, these functions
+// return a Maybe object which contains a value if and only if the
+// result is meaningful
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::PointTyped<TargetUnits>> UntransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aPoint) {
+ gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(aPoint);
+ if (!point.HasPositiveWCoord()) {
+ return Nothing();
+ }
+ return Some(point.As2DPoint());
+}
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::IntPointTyped<TargetUnits>> UntransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntPointTyped<SourceUnits>& aPoint) {
+ gfx::PointTyped<SourceUnits> p = aPoint;
+ gfx::Point4DTyped<TargetUnits> point = aTransform.ProjectPoint(p);
+ if (!point.HasPositiveWCoord()) {
+ return Nothing();
+ }
+ return Some(RoundedToInt(point.As2DPoint()));
+}
+
+// The versions of UntransformBy() that take a rectangle also take a clip,
+// which represents the bounds within which the target must fall. The
+// result of the transform is intersected with this clip, and is considered
+// meaningful if the intersection is not empty.
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::RectTyped<TargetUnits>> UntransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::RectTyped<SourceUnits>& aRect,
+ const gfx::RectTyped<TargetUnits>& aClip) {
+ gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
+ if (rect.IsEmpty()) {
+ return Nothing();
+ }
+ return Some(rect);
+}
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::IntRectTyped<TargetUnits>> UntransformBy(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::IntRectTyped<SourceUnits>& aRect,
+ const gfx::IntRectTyped<TargetUnits>& aClip) {
+ gfx::RectTyped<TargetUnits> rect = aTransform.ProjectRectBounds(aRect, aClip);
+ if (rect.IsEmpty()) {
+ return Nothing();
+ }
+ return Some(RoundedToInt(rect));
+}
+
+template <typename TargetUnits, typename SourceUnits>
+static Maybe<gfx::PointTyped<TargetUnits>> UntransformVector(
+ const gfx::Matrix4x4Typed<SourceUnits, TargetUnits>& aTransform,
+ const gfx::PointTyped<SourceUnits>& aVector,
+ const gfx::PointTyped<SourceUnits>& aAnchor) {
+ gfx::Point4DTyped<TargetUnits> projectedAnchor =
+ aTransform.ProjectPoint(aAnchor);
+ gfx::Point4DTyped<TargetUnits> projectedTarget =
+ aTransform.ProjectPoint(aAnchor + aVector);
+ if (!projectedAnchor.HasPositiveWCoord() ||
+ !projectedTarget.HasPositiveWCoord()) {
+ return Nothing();
+ }
+ return Some(projectedTarget.As2DPoint() - projectedAnchor.As2DPoint());
+}
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/Units.h b/layout/base/Units.h
new file mode 100644
index 0000000000..465585ff1b
--- /dev/null
+++ b/layout/base/Units.h
@@ -0,0 +1,968 @@
+/* -*- 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 MOZ_UNITS_H_
+#define MOZ_UNITS_H_
+
+#include <type_traits>
+
+#include "mozilla/gfx/Coord.h"
+#include "mozilla/gfx/Point.h"
+#include "mozilla/gfx/Rect.h"
+#include "mozilla/gfx/RectAbsolute.h"
+#include "mozilla/gfx/ScaleFactor.h"
+#include "mozilla/gfx/ScaleFactors2D.h"
+#include "nsMargin.h"
+#include "nsRect.h"
+#include "nsRegion.h"
+#include "mozilla/AppUnits.h"
+
+namespace mozilla {
+
+template <typename T>
+struct IsPixel : std::false_type {};
+
+// See struct declaration for a description of each unit type.
+struct CSSPixel;
+struct OuterCSSPixel;
+struct LayoutDevicePixel;
+struct LayerPixel;
+struct CSSTransformedLayerPixel;
+struct RenderTargetPixel;
+struct ScreenPixel;
+struct ParentLayerPixel;
+struct DesktopPixel;
+struct ImagePixel;
+struct ExternalPixel;
+
+template <>
+struct IsPixel<CSSPixel> : std::true_type {};
+template <>
+struct IsPixel<OuterCSSPixel> : std::true_type {};
+template <>
+struct IsPixel<LayoutDevicePixel> : std::true_type {};
+template <>
+struct IsPixel<LayerPixel> : std::true_type {};
+template <>
+struct IsPixel<CSSTransformedLayerPixel> : std::true_type {};
+template <>
+struct IsPixel<RenderTargetPixel> : std::true_type {};
+template <>
+struct IsPixel<ImagePixel> : std::true_type {};
+template <>
+struct IsPixel<ScreenPixel> : std::true_type {};
+template <>
+struct IsPixel<ParentLayerPixel> : std::true_type {};
+template <>
+struct IsPixel<DesktopPixel> : std::true_type {};
+template <>
+struct IsPixel<ExternalPixel> : std::true_type {};
+
+typedef gfx::CoordTyped<CSSPixel> CSSCoord;
+typedef gfx::IntCoordTyped<CSSPixel> CSSIntCoord;
+typedef gfx::PointTyped<CSSPixel> CSSPoint;
+typedef gfx::IntPointTyped<CSSPixel> CSSIntPoint;
+typedef gfx::SizeTyped<CSSPixel> CSSSize;
+typedef gfx::IntSizeTyped<CSSPixel> CSSIntSize;
+typedef gfx::RectTyped<CSSPixel> CSSRect;
+typedef gfx::IntRectTyped<CSSPixel> CSSIntRect;
+typedef gfx::MarginTyped<CSSPixel> CSSMargin;
+typedef gfx::IntMarginTyped<CSSPixel> CSSIntMargin;
+typedef gfx::IntRegionTyped<CSSPixel> CSSIntRegion;
+
+typedef gfx::CoordTyped<OuterCSSPixel> OuterCSSCoord;
+typedef gfx::IntCoordTyped<OuterCSSPixel> OuterCSSIntCoord;
+typedef gfx::PointTyped<OuterCSSPixel> OuterCSSPoint;
+typedef gfx::IntPointTyped<OuterCSSPixel> OuterCSSIntPoint;
+typedef gfx::SizeTyped<OuterCSSPixel> OuterCSSSize;
+typedef gfx::IntSizeTyped<OuterCSSPixel> OuterCSSIntSize;
+typedef gfx::RectTyped<OuterCSSPixel> OuterCSSRect;
+typedef gfx::IntRectTyped<OuterCSSPixel> OuterCSSIntRect;
+typedef gfx::MarginTyped<OuterCSSPixel> OuterCSSMargin;
+typedef gfx::IntMarginTyped<OuterCSSPixel> OuterCSSIntMargin;
+typedef gfx::IntRegionTyped<OuterCSSPixel> OuterCSSIntRegion;
+
+typedef gfx::CoordTyped<LayoutDevicePixel> LayoutDeviceCoord;
+typedef gfx::IntCoordTyped<LayoutDevicePixel> LayoutDeviceIntCoord;
+typedef gfx::PointTyped<LayoutDevicePixel> LayoutDevicePoint;
+typedef gfx::IntPointTyped<LayoutDevicePixel> LayoutDeviceIntPoint;
+typedef gfx::SizeTyped<LayoutDevicePixel> LayoutDeviceSize;
+typedef gfx::IntSizeTyped<LayoutDevicePixel> LayoutDeviceIntSize;
+typedef gfx::RectTyped<LayoutDevicePixel> LayoutDeviceRect;
+typedef gfx::IntRectTyped<LayoutDevicePixel> LayoutDeviceIntRect;
+typedef gfx::MarginTyped<LayoutDevicePixel> LayoutDeviceMargin;
+typedef gfx::IntMarginTyped<LayoutDevicePixel> LayoutDeviceIntMargin;
+typedef gfx::IntRegionTyped<LayoutDevicePixel> LayoutDeviceIntRegion;
+
+typedef gfx::CoordTyped<LayerPixel> LayerCoord;
+typedef gfx::IntCoordTyped<LayerPixel> LayerIntCoord;
+typedef gfx::PointTyped<LayerPixel> LayerPoint;
+typedef gfx::IntPointTyped<LayerPixel> LayerIntPoint;
+typedef gfx::SizeTyped<LayerPixel> LayerSize;
+typedef gfx::IntSizeTyped<LayerPixel> LayerIntSize;
+typedef gfx::RectTyped<LayerPixel> LayerRect;
+typedef gfx::RectAbsoluteTyped<LayerPixel> LayerRectAbsolute;
+typedef gfx::IntRectTyped<LayerPixel> LayerIntRect;
+typedef gfx::MarginTyped<LayerPixel> LayerMargin;
+typedef gfx::IntMarginTyped<LayerPixel> LayerIntMargin;
+typedef gfx::IntRegionTyped<LayerPixel> LayerIntRegion;
+
+typedef gfx::CoordTyped<CSSTransformedLayerPixel> CSSTransformedLayerCoord;
+typedef gfx::IntCoordTyped<CSSTransformedLayerPixel>
+ CSSTransformedLayerIntCoord;
+typedef gfx::PointTyped<CSSTransformedLayerPixel> CSSTransformedLayerPoint;
+typedef gfx::IntPointTyped<CSSTransformedLayerPixel>
+ CSSTransformedLayerIntPoint;
+typedef gfx::SizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerSize;
+typedef gfx::IntSizeTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntSize;
+typedef gfx::RectTyped<CSSTransformedLayerPixel> CSSTransformedLayerRect;
+typedef gfx::IntRectTyped<CSSTransformedLayerPixel> CSSTransformedLayerIntRect;
+typedef gfx::MarginTyped<CSSTransformedLayerPixel> CSSTransformedLayerMargin;
+typedef gfx::IntMarginTyped<CSSTransformedLayerPixel>
+ CSSTransformedLayerIntMargin;
+typedef gfx::IntRegionTyped<CSSTransformedLayerPixel>
+ CSSTransformedLayerIntRegion;
+
+typedef gfx::PointTyped<RenderTargetPixel> RenderTargetPoint;
+typedef gfx::IntPointTyped<RenderTargetPixel> RenderTargetIntPoint;
+typedef gfx::SizeTyped<RenderTargetPixel> RenderTargetSize;
+typedef gfx::IntSizeTyped<RenderTargetPixel> RenderTargetIntSize;
+typedef gfx::RectTyped<RenderTargetPixel> RenderTargetRect;
+typedef gfx::IntRectTyped<RenderTargetPixel> RenderTargetIntRect;
+typedef gfx::MarginTyped<RenderTargetPixel> RenderTargetMargin;
+typedef gfx::IntMarginTyped<RenderTargetPixel> RenderTargetIntMargin;
+typedef gfx::IntRegionTyped<RenderTargetPixel> RenderTargetIntRegion;
+
+typedef gfx::PointTyped<ImagePixel> ImagePoint;
+typedef gfx::IntPointTyped<ImagePixel> ImageIntPoint;
+typedef gfx::SizeTyped<ImagePixel> ImageSize;
+typedef gfx::IntSizeTyped<ImagePixel> ImageIntSize;
+typedef gfx::RectTyped<ImagePixel> ImageRect;
+typedef gfx::IntRectTyped<ImagePixel> ImageIntRect;
+
+typedef gfx::CoordTyped<ScreenPixel> ScreenCoord;
+typedef gfx::IntCoordTyped<ScreenPixel> ScreenIntCoord;
+typedef gfx::PointTyped<ScreenPixel> ScreenPoint;
+typedef gfx::IntPointTyped<ScreenPixel> ScreenIntPoint;
+typedef gfx::SizeTyped<ScreenPixel> ScreenSize;
+typedef gfx::IntSizeTyped<ScreenPixel> ScreenIntSize;
+typedef gfx::RectTyped<ScreenPixel> ScreenRect;
+typedef gfx::IntRectTyped<ScreenPixel> ScreenIntRect;
+typedef gfx::MarginTyped<ScreenPixel> ScreenMargin;
+typedef gfx::IntMarginTyped<ScreenPixel> ScreenIntMargin;
+typedef gfx::IntRegionTyped<ScreenPixel> ScreenIntRegion;
+
+typedef gfx::CoordTyped<ParentLayerPixel> ParentLayerCoord;
+typedef gfx::IntCoordTyped<ParentLayerPixel> ParentLayerIntCoord;
+typedef gfx::PointTyped<ParentLayerPixel> ParentLayerPoint;
+typedef gfx::IntPointTyped<ParentLayerPixel> ParentLayerIntPoint;
+typedef gfx::SizeTyped<ParentLayerPixel> ParentLayerSize;
+typedef gfx::IntSizeTyped<ParentLayerPixel> ParentLayerIntSize;
+typedef gfx::RectTyped<ParentLayerPixel> ParentLayerRect;
+typedef gfx::IntRectTyped<ParentLayerPixel> ParentLayerIntRect;
+typedef gfx::MarginTyped<ParentLayerPixel> ParentLayerMargin;
+typedef gfx::IntMarginTyped<ParentLayerPixel> ParentLayerIntMargin;
+typedef gfx::IntRegionTyped<ParentLayerPixel> ParentLayerIntRegion;
+
+typedef gfx::CoordTyped<DesktopPixel> DesktopCoord;
+typedef gfx::IntCoordTyped<DesktopPixel> DesktopIntCoord;
+typedef gfx::PointTyped<DesktopPixel> DesktopPoint;
+typedef gfx::IntPointTyped<DesktopPixel> DesktopIntPoint;
+typedef gfx::SizeTyped<DesktopPixel> DesktopSize;
+typedef gfx::IntSizeTyped<DesktopPixel> DesktopIntSize;
+typedef gfx::RectTyped<DesktopPixel> DesktopRect;
+typedef gfx::IntRectTyped<DesktopPixel> DesktopIntRect;
+
+typedef gfx::CoordTyped<ExternalPixel> ExternalCoord;
+typedef gfx::IntCoordTyped<ExternalPixel> ExternalIntCoord;
+typedef gfx::PointTyped<ExternalPixel> ExternalPoint;
+typedef gfx::IntPointTyped<ExternalPixel> ExternalIntPoint;
+typedef gfx::SizeTyped<ExternalPixel> ExternalSize;
+typedef gfx::IntSizeTyped<ExternalPixel> ExternalIntSize;
+typedef gfx::RectTyped<ExternalPixel> ExternalRect;
+typedef gfx::IntRectTyped<ExternalPixel> ExternalIntRect;
+typedef gfx::MarginTyped<ExternalPixel> ExternalMargin;
+typedef gfx::IntMarginTyped<ExternalPixel> ExternalIntMargin;
+typedef gfx::IntRegionTyped<ExternalPixel> ExternalIntRegion;
+
+typedef gfx::ScaleFactor<CSSPixel, CSSPixel> CSSToCSSScale;
+typedef gfx::ScaleFactor<CSSPixel, OuterCSSPixel> CSSToOuterCSSScale;
+typedef gfx::ScaleFactor<CSSPixel, LayoutDevicePixel> CSSToLayoutDeviceScale;
+typedef gfx::ScaleFactor<CSSPixel, LayerPixel> CSSToLayerScale;
+typedef gfx::ScaleFactor<CSSPixel, ScreenPixel> CSSToScreenScale;
+typedef gfx::ScaleFactor<CSSPixel, ParentLayerPixel> CSSToParentLayerScale;
+typedef gfx::ScaleFactor<CSSPixel, DesktopPixel> CSSToDesktopScale;
+typedef gfx::ScaleFactor<OuterCSSPixel, LayoutDevicePixel>
+ OuterCSSToLayoutDeviceScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, CSSPixel> LayoutDeviceToCSSScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, LayerPixel>
+ LayoutDeviceToLayerScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, ScreenPixel>
+ LayoutDeviceToScreenScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, ParentLayerPixel>
+ LayoutDeviceToParentLayerScale;
+typedef gfx::ScaleFactor<LayerPixel, CSSPixel> LayerToCSSScale;
+typedef gfx::ScaleFactor<LayerPixel, LayoutDevicePixel>
+ LayerToLayoutDeviceScale;
+typedef gfx::ScaleFactor<LayerPixel, RenderTargetPixel>
+ LayerToRenderTargetScale;
+typedef gfx::ScaleFactor<LayerPixel, ScreenPixel> LayerToScreenScale;
+typedef gfx::ScaleFactor<LayerPixel, ParentLayerPixel> LayerToParentLayerScale;
+typedef gfx::ScaleFactor<RenderTargetPixel, ScreenPixel>
+ RenderTargetToScreenScale;
+typedef gfx::ScaleFactor<ScreenPixel, CSSPixel> ScreenToCSSScale;
+typedef gfx::ScaleFactor<ScreenPixel, LayoutDevicePixel>
+ ScreenToLayoutDeviceScale;
+typedef gfx::ScaleFactor<ScreenPixel, LayerPixel> ScreenToLayerScale;
+typedef gfx::ScaleFactor<ScreenPixel, ParentLayerPixel>
+ ScreenToParentLayerScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, LayerPixel> ParentLayerToLayerScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, ScreenPixel>
+ ParentLayerToScreenScale;
+typedef gfx::ScaleFactor<ParentLayerPixel, ParentLayerPixel>
+ ParentLayerToParentLayerScale;
+typedef gfx::ScaleFactor<DesktopPixel, LayoutDevicePixel>
+ DesktopToLayoutDeviceScale;
+typedef gfx::ScaleFactor<LayoutDevicePixel, DesktopPixel>
+ LayoutDeviceToDesktopScale;
+
+typedef gfx::ScaleFactors2D<CSSPixel, LayoutDevicePixel>
+ CSSToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, LayerPixel> CSSToLayerScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, ScreenPixel> CSSToScreenScale2D;
+typedef gfx::ScaleFactors2D<CSSPixel, ParentLayerPixel> CSSToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, CSSPixel>
+ LayoutDeviceToCSSScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, LayerPixel>
+ LayoutDeviceToLayerScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, ScreenPixel>
+ LayoutDeviceToScreenScale2D;
+typedef gfx::ScaleFactors2D<LayoutDevicePixel, ParentLayerPixel>
+ LayoutDeviceToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, CSSPixel> LayerToCSSScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, LayoutDevicePixel>
+ LayerToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, RenderTargetPixel>
+ LayerToRenderTargetScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, ScreenPixel> LayerToScreenScale2D;
+typedef gfx::ScaleFactors2D<LayerPixel, ParentLayerPixel>
+ LayerToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<RenderTargetPixel, ScreenPixel>
+ RenderTargetToScreenScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, CSSPixel> ScreenToCSSScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, LayoutDevicePixel>
+ ScreenToLayoutDeviceScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, ScreenPixel> ScreenToScreenScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, LayerPixel> ScreenToLayerScale2D;
+typedef gfx::ScaleFactors2D<ScreenPixel, ParentLayerPixel>
+ ScreenToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, LayerPixel>
+ ParentLayerToLayerScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, ScreenPixel>
+ ParentLayerToScreenScale2D;
+typedef gfx::ScaleFactors2D<ParentLayerPixel, ParentLayerPixel>
+ ParentLayerToParentLayerScale2D;
+typedef gfx::ScaleFactors2D<gfx::UnknownUnits, gfx::UnknownUnits> Scale2D;
+
+typedef gfx::Matrix4x4Typed<CSSPixel, CSSPixel> CSSToCSSMatrix4x4;
+typedef gfx::Matrix4x4Typed<LayoutDevicePixel, LayoutDevicePixel>
+ LayoutDeviceToLayoutDeviceMatrix4x4;
+typedef gfx::Matrix4x4Typed<LayoutDevicePixel, ParentLayerPixel>
+ LayoutDeviceToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<LayerPixel, ParentLayerPixel>
+ LayerToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<LayerPixel, ScreenPixel> LayerToScreenMatrix4x4;
+typedef gfx::Matrix4x4Typed<ScreenPixel, ScreenPixel> ScreenToScreenMatrix4x4;
+typedef gfx::Matrix4x4Typed<ScreenPixel, ParentLayerPixel>
+ ScreenToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, LayerPixel>
+ ParentLayerToLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, ScreenPixel>
+ ParentLayerToScreenMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, ParentLayerPixel>
+ ParentLayerToParentLayerMatrix4x4;
+typedef gfx::Matrix4x4Typed<ParentLayerPixel, RenderTargetPixel>
+ ParentLayerToRenderTargetMatrix4x4;
+typedef gfx::Matrix4x4Typed<ExternalPixel, ParentLayerPixel>
+ ExternalToParentLayerMatrix4x4;
+
+/*
+ * The pixels that content authors use to specify sizes in.
+ */
+struct CSSPixel {
+ // Conversions from app units
+ static CSSCoord FromAppUnits(nscoord aCoord) {
+ return NSAppUnitsToFloatPixels(aCoord, float(AppUnitsPerCSSPixel()));
+ }
+
+ static CSSIntCoord FromAppUnitsRounded(nscoord aCoord) {
+ return NSAppUnitsToIntPixels(aCoord, float(AppUnitsPerCSSPixel()));
+ }
+
+ static CSSPoint FromAppUnits(const nsPoint& aPoint) {
+ return CSSPoint(FromAppUnits(aPoint.x), FromAppUnits(aPoint.y));
+ }
+
+ static CSSSize FromAppUnits(const nsSize& aSize) {
+ return CSSSize(FromAppUnits(aSize.width), FromAppUnits(aSize.height));
+ }
+
+ static CSSRect FromAppUnits(const nsRect& aRect) {
+ return CSSRect(FromAppUnits(aRect.x), FromAppUnits(aRect.y),
+ FromAppUnits(aRect.Width()), FromAppUnits(aRect.Height()));
+ }
+
+ static CSSMargin FromAppUnits(const nsMargin& aMargin) {
+ return CSSMargin(FromAppUnits(aMargin.top), FromAppUnits(aMargin.right),
+ FromAppUnits(aMargin.bottom), FromAppUnits(aMargin.left));
+ }
+
+ static CSSIntPoint FromAppUnitsRounded(const nsPoint& aPoint) {
+ return CSSIntPoint(FromAppUnitsRounded(aPoint.x),
+ FromAppUnitsRounded(aPoint.y));
+ }
+
+ static CSSIntSize FromAppUnitsRounded(const nsSize& aSize) {
+ return CSSIntSize(FromAppUnitsRounded(aSize.width),
+ FromAppUnitsRounded(aSize.height));
+ }
+
+ static CSSIntRect FromAppUnitsRounded(const nsRect& aRect) {
+ return CSSIntRect(FromAppUnitsRounded(aRect.x),
+ FromAppUnitsRounded(aRect.y),
+ FromAppUnitsRounded(aRect.Width()),
+ FromAppUnitsRounded(aRect.Height()));
+ }
+
+ static CSSIntMargin FromAppUnitsRounded(const nsMargin& aMargin) {
+ return CSSIntMargin(
+ FromAppUnitsRounded(aMargin.top), FromAppUnitsRounded(aMargin.right),
+ FromAppUnitsRounded(aMargin.bottom), FromAppUnitsRounded(aMargin.left));
+ }
+
+ static CSSIntRect FromAppUnitsToNearest(const nsRect& aRect) {
+ return CSSIntRect::FromUnknownRect(
+ aRect.ToNearestPixels(AppUnitsPerCSSPixel()));
+ }
+
+ static CSSIntRect FromAppUnitsToInside(const nsRect& aRect) {
+ return CSSIntRect::FromUnknownRect(
+ aRect.ToInsidePixels(AppUnitsPerCSSPixel()));
+ }
+
+ // Conversions to app units
+
+ // TODO: We might want an int32_t/CSSIntCoord overload which doesn't do float
+ // math but we'd need to ensure stuff is clamped to nscoord_MIN/MAX range.
+ static nscoord ToAppUnits(CSSCoord aCoord) {
+ return NSFloatPixelsToAppUnits(aCoord, AppUnitsPerCSSPixel());
+ }
+
+ static nsPoint ToAppUnits(const CSSPoint& aPoint) {
+ return nsPoint(ToAppUnits(aPoint.x), ToAppUnits(aPoint.y));
+ }
+
+ static nsPoint ToAppUnits(const CSSIntPoint& aPoint) {
+ return nsPoint(ToAppUnits(CSSCoord(aPoint.x)),
+ ToAppUnits(CSSCoord(aPoint.y)));
+ }
+
+ static nsSize ToAppUnits(const CSSSize& aSize) {
+ return nsSize(ToAppUnits(aSize.width), ToAppUnits(aSize.height));
+ }
+
+ static nsSize ToAppUnits(const CSSIntSize& aSize) {
+ return nsSize(ToAppUnits(aSize.width), ToAppUnits(aSize.height));
+ }
+
+ static nsRect ToAppUnits(const CSSRect& aRect) {
+ return nsRect(ToAppUnits(aRect.x), ToAppUnits(aRect.y),
+ ToAppUnits(aRect.Width()), ToAppUnits(aRect.Height()));
+ }
+
+ static nsRect ToAppUnits(const CSSIntRect& aRect) {
+ return nsRect(ToAppUnits(aRect.x), ToAppUnits(aRect.y),
+ ToAppUnits(aRect.Width()), ToAppUnits(aRect.Height()));
+ }
+
+ static nsMargin ToAppUnits(const CSSMargin& aMargin) {
+ return nsMargin(ToAppUnits(aMargin.top), ToAppUnits(aMargin.right),
+ ToAppUnits(aMargin.bottom), ToAppUnits(aMargin.left));
+ }
+
+ static nsMargin ToAppUnits(const CSSIntMargin& aMargin) {
+ return nsMargin(ToAppUnits(CSSCoord(aMargin.top)),
+ ToAppUnits(CSSCoord(aMargin.right)),
+ ToAppUnits(CSSCoord(aMargin.bottom)),
+ ToAppUnits(CSSCoord(aMargin.left)));
+ }
+
+ // Conversion from a given CSS point value.
+ static CSSCoord FromPoints(float aCoord) {
+ // One inch / 72.
+ return aCoord * 96.0f / 72.0f;
+ }
+};
+
+/*
+ * In the context of a scroll frame that is zoomable, OuterCSSPixel is
+ * used to disambiguate CSS pixels of the content outside of the scroll
+ * frame (which is not subject to the zoom) from CSS pixels of the content
+ * inside the scroll frame (which is, and for which CSSPixel is used).
+ */
+struct OuterCSSPixel {
+ static OuterCSSCoord FromAppUnits(nscoord aCoord) {
+ return NSAppUnitsToFloatPixels(aCoord, float(AppUnitsPerCSSPixel()));
+ }
+};
+
+/*
+ * The pixels that are referred to as "device pixels" in layout code. In
+ * general values measured in LayoutDevicePixels are obtained by dividing a
+ * value in app units by AppUnitsPerDevPixel(). Conversion between CSS pixels
+ * and LayoutDevicePixels is affected by:
+ * 1) the "full zoom" (see nsPresContext::SetFullZoom)
+ * 2) the "widget scale" (see nsIWidget::GetDefaultScale)
+ */
+struct LayoutDevicePixel {
+ static LayoutDeviceCoord FromAppUnits(nscoord aCoord,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceCoord(
+ NSAppUnitsToFloatPixels(aCoord, float(aAppUnitsPerDevPixel)));
+ }
+
+ static LayoutDeviceIntCoord FromAppUnitsRounded(
+ nscoord aCoord, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntCoord(
+ NSAppUnitsToIntPixels(aCoord, float(aAppUnitsPerDevPixel)));
+ }
+
+ static LayoutDeviceRect FromAppUnits(const nsRect& aRect,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceRect(FromAppUnits(aRect.x, aAppUnitsPerDevPixel),
+ FromAppUnits(aRect.y, aAppUnitsPerDevPixel),
+ FromAppUnits(aRect.Width(), aAppUnitsPerDevPixel),
+ FromAppUnits(aRect.Height(), aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceSize FromAppUnits(const nsSize& aSize,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceSize(FromAppUnits(aSize.width, aAppUnitsPerDevPixel),
+ FromAppUnits(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDevicePoint FromAppUnits(const nsPoint& aPoint,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDevicePoint(FromAppUnits(aPoint.x, aAppUnitsPerDevPixel),
+ FromAppUnits(aPoint.y, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceMargin FromAppUnits(const nsMargin& aMargin,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceMargin(
+ FromAppUnits(aMargin.top, aAppUnitsPerDevPixel),
+ FromAppUnits(aMargin.right, aAppUnitsPerDevPixel),
+ FromAppUnits(aMargin.bottom, aAppUnitsPerDevPixel),
+ FromAppUnits(aMargin.left, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntPoint FromAppUnitsRounded(
+ const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntPoint(
+ FromAppUnitsRounded(aPoint.x, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aPoint.y, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsRounded(const nsRect& aRect,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect(
+ FromAppUnitsRounded(aRect.x, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aRect.y, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aRect.Width(), aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aRect.Height(), aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntPoint FromAppUnitsToNearest(
+ const nsPoint& aPoint, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntPoint::FromUnknownPoint(
+ aPoint.ToNearestPixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsToNearest(
+ const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect::FromUnknownRect(
+ aRect.ToNearestPixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsToInside(
+ const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect::FromUnknownRect(
+ aRect.ToInsidePixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntRect FromAppUnitsToOutside(
+ const nsRect& aRect, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntRect::FromUnknownRect(
+ aRect.ToOutsidePixels(aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntSize FromAppUnitsRounded(const nsSize& aSize,
+ nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntSize(
+ FromAppUnitsRounded(aSize.width, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static LayoutDeviceIntMargin FromAppUnitsRounded(
+ const nsMargin& aMargin, nscoord aAppUnitsPerDevPixel) {
+ return LayoutDeviceIntMargin(
+ FromAppUnitsRounded(aMargin.top, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aMargin.right, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aMargin.bottom, aAppUnitsPerDevPixel),
+ FromAppUnitsRounded(aMargin.left, aAppUnitsPerDevPixel));
+ }
+
+ static nscoord ToAppUnits(LayoutDeviceIntCoord aCoord,
+ nscoord aAppUnitsPerDevPixel) {
+ return aCoord * aAppUnitsPerDevPixel;
+ }
+
+ static nscoord ToAppUnits(int32_t aCoord, nscoord aAppUnitsPerDevPixel) {
+ return ToAppUnits(LayoutDeviceIntCoord(aCoord), aAppUnitsPerDevPixel);
+ }
+
+ static nscoord ToAppUnits(LayoutDeviceCoord aCoord,
+ nscoord aAppUnitsPerDevPixel) {
+ return NSFloatPixelsToAppUnits(aCoord, aAppUnitsPerDevPixel);
+ }
+
+ static nscoord ToAppUnits(float aCoord, nscoord aAppUnitsPerDevPixel) {
+ return ToAppUnits(LayoutDeviceCoord(aCoord), aAppUnitsPerDevPixel);
+ }
+
+ static nsPoint ToAppUnits(const LayoutDeviceIntPoint& aPoint,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsPoint(ToAppUnits(aPoint.x, aAppUnitsPerDevPixel),
+ ToAppUnits(aPoint.y, aAppUnitsPerDevPixel));
+ }
+
+ static nsSize ToAppUnits(const LayoutDeviceIntSize& aSize,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsSize(ToAppUnits(aSize.width, aAppUnitsPerDevPixel),
+ ToAppUnits(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static nsSize ToAppUnits(const LayoutDeviceSize& aSize,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsSize(ToAppUnits(aSize.width, aAppUnitsPerDevPixel),
+ ToAppUnits(aSize.height, aAppUnitsPerDevPixel));
+ }
+
+ static nsRect ToAppUnits(const LayoutDeviceIntRect& aRect,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsRect(ToAppUnits(aRect.x, aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.y, aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.Width(), aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.Height(), aAppUnitsPerDevPixel));
+ }
+
+ static nsRect ToAppUnits(const LayoutDeviceRect& aRect,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsRect(ToAppUnits(aRect.x, aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.y, aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.Width(), aAppUnitsPerDevPixel),
+ ToAppUnits(aRect.Height(), aAppUnitsPerDevPixel));
+ }
+
+ static nsMargin ToAppUnits(const LayoutDeviceIntMargin& aMargin,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsMargin(ToAppUnits(aMargin.top, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.right, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.bottom, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.left, aAppUnitsPerDevPixel));
+ }
+
+ static nsMargin ToAppUnits(const LayoutDeviceMargin& aMargin,
+ nscoord aAppUnitsPerDevPixel) {
+ return nsMargin(ToAppUnits(aMargin.top, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.right, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.bottom, aAppUnitsPerDevPixel),
+ ToAppUnits(aMargin.left, aAppUnitsPerDevPixel));
+ }
+};
+
+/*
+ * The pixels that layout rasterizes and delivers to the graphics code.
+ * These also are generally referred to as "device pixels" in layout code.
+ * Conversion between CSS pixels and LayerPixels is affected by:
+ * 1) the "display resolution" (see PresShell::SetResolution)
+ * 2) the "full zoom" (see nsPresContext::SetFullZoom)
+ * 3) the "widget scale" (see nsIWidget::GetDefaultScale)
+ * 4) rasterizing at a different scale in the presence of some CSS transforms
+ */
+struct LayerPixel {};
+
+/*
+ * This is Layer coordinates with the Layer's CSS transform applied.
+ * It can be thought of as intermediate between LayerPixel and
+ * ParentLayerPixel, as further applying async transforms to this
+ * yields ParentLayerPixels.
+ */
+struct CSSTransformedLayerPixel {};
+
+/*
+ * Layers are always composited to a render target. This unit
+ * represents one pixel in the render target. Note that for the
+ * root render target RenderTargetPixel == ScreenPixel. Also
+ * any ContainerLayer providing an intermediate surface will
+ * have RenderTargetPixel == LayerPixel.
+ */
+struct RenderTargetPixel {};
+
+/*
+ * This unit represents one pixel in an image. Image space
+ * is largely independent of any other space.
+ */
+struct ImagePixel {};
+
+/*
+ * The pixels that are displayed on the screen.
+ * On non-OMTC platforms this should be equivalent to LayerPixel units.
+ * On OMTC platforms these may diverge from LayerPixel units temporarily,
+ * while an asynchronous zoom is happening, but should eventually converge
+ * back to LayerPixel units. Some variables (such as those representing
+ * chrome UI element sizes) that are not subject to content zoom should
+ * generally be represented in ScreenPixel units.
+ */
+struct ScreenPixel {};
+
+/* The layer coordinates of the parent frame.
+ * This can be arrived at in three ways:
+ * - Start with the CSS coordinates of the parent frame, multiply by the
+ * device scale and the cumulative resolution of the parent frame.
+ * - Start with the CSS coordinates of current frame, multiply by the device
+ * scale, the cumulative resolution of the current frame, and the scales
+ * from the CSS and async transforms of the current frame.
+ * - Start with global screen coordinates and unapply all CSS and async
+ * transforms from the root down to and including the parent.
+ * It's helpful to look at
+ * https://wiki.mozilla.org/Platform/GFX/APZ#Coordinate_systems to get a picture
+ * of how the various coordinate systems relate to each other.
+ */
+struct ParentLayerPixel {};
+
+/*
+ * Pixels in the coordinate space used by the host OS to manage windows on the
+ * desktop. What these mean varies between OSs:
+ * - by default (unless implemented differently in platform-specific widget
+ * code) they are the same as LayoutDevicePixels
+ * - on Mac OS X, they're "cocoa points", which correspond to device pixels
+ * on a non-Retina display, and to 2x device pixels on Retina
+ * - on Windows *without* per-monitor DPI support, they are Windows "logical
+ * pixels", i.e. device pixels scaled according to the Windows display DPI
+ * scaling factor (typically one of 1.25, 1.5, 1.75, 2.0...)
+ * - on Windows *with* per-monitor DPI support, they are physical device pixels
+ * on each screen; note that this means the scaling between CSS pixels and
+ * desktop pixels may vary across multiple displays.
+ */
+struct DesktopPixel {};
+
+struct ExternalPixel {};
+
+// Operators to apply ScaleFactors directly to Coords, Points, Rects, Sizes and
+// Margins
+
+template <class Src, class Dst>
+gfx::CoordTyped<Dst> operator*(const gfx::CoordTyped<Src>& aCoord,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::CoordTyped<Dst>(aCoord.value * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::CoordTyped<Dst> operator/(const gfx::CoordTyped<Src>& aCoord,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::CoordTyped<Dst>(aCoord.value / aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::PointTyped<Dst> operator*(const gfx::PointTyped<Src>& aPoint,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::PointTyped<Dst>(aPoint.x * aScale.scale, aPoint.y * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::PointTyped<Dst> operator/(const gfx::PointTyped<Src>& aPoint,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::PointTyped<Dst>(aPoint.x / aScale.scale, aPoint.y / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::PointTyped<Dst, F> operator*(
+ const gfx::PointTyped<Src, F>& aPoint,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::PointTyped<Dst, F>(aPoint.x * aScale.xScale,
+ aPoint.y * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::PointTyped<Dst, F> operator/(
+ const gfx::PointTyped<Src, F>& aPoint,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::PointTyped<Dst, F>(aPoint.x / aScale.xScale,
+ aPoint.y / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::PointTyped<Dst> operator*(const gfx::IntPointTyped<Src>& aPoint,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::PointTyped<Dst>(float(aPoint.x) * aScale.scale,
+ float(aPoint.y) * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::PointTyped<Dst> operator/(const gfx::IntPointTyped<Src>& aPoint,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::PointTyped<Dst>(float(aPoint.x) / aScale.scale,
+ float(aPoint.y) / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::PointTyped<Dst, F> operator*(
+ const gfx::IntPointTyped<Src>& aPoint,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::PointTyped<Dst, F>(F(aPoint.x) * aScale.xScale,
+ F(aPoint.y) * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::PointTyped<Dst, F> operator/(
+ const gfx::IntPointTyped<Src>& aPoint,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::PointTyped<Dst, F>(F(aPoint.x) / aScale.xScale,
+ F(aPoint.y) / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::RectTyped<Dst> operator*(const gfx::RectTyped<Src>& aRect,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::RectTyped<Dst>(aRect.x * aScale.scale, aRect.y * aScale.scale,
+ aRect.Width() * aScale.scale,
+ aRect.Height() * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::RectTyped<Dst> operator/(const gfx::RectTyped<Src>& aRect,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::RectTyped<Dst>(aRect.x / aScale.scale, aRect.y / aScale.scale,
+ aRect.Width() / aScale.scale,
+ aRect.Height() / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::RectTyped<Dst, F> operator*(
+ const gfx::RectTyped<Src, F>& aRect,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::RectTyped<Dst, F>(
+ aRect.x * aScale.xScale, aRect.y * aScale.yScale,
+ aRect.Width() * aScale.xScale, aRect.Height() * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::RectTyped<Dst, F> operator/(
+ const gfx::RectTyped<Src, F>& aRect,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::RectTyped<Dst, F>(
+ aRect.x / aScale.xScale, aRect.y / aScale.yScale,
+ aRect.Width() / aScale.xScale, aRect.Height() / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::RectTyped<Dst> operator*(const gfx::IntRectTyped<Src>& aRect,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::RectTyped<Dst>(float(aRect.x) * aScale.scale,
+ float(aRect.y) * aScale.scale,
+ float(aRect.Width()) * aScale.scale,
+ float(aRect.Height()) * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::RectTyped<Dst> operator/(const gfx::IntRectTyped<Src>& aRect,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::RectTyped<Dst>(float(aRect.x) / aScale.scale,
+ float(aRect.y) / aScale.scale,
+ float(aRect.Width()) / aScale.scale,
+ float(aRect.Height()) / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::RectTyped<Dst, F> operator*(
+ const gfx::IntRectTyped<Src>& aRect,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::RectTyped<Dst, F>(
+ F(aRect.x) * aScale.xScale, F(aRect.y) * aScale.yScale,
+ F(aRect.Width()) * aScale.xScale, F(aRect.Height()) * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::RectTyped<Dst, F> operator/(
+ const gfx::IntRectTyped<Src>& aRect,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::RectTyped<Dst, F>(
+ F(aRect.x) / aScale.xScale, F(aRect.y) / aScale.yScale,
+ F(aRect.Width()) / aScale.xScale, F(aRect.Height()) / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::SizeTyped<Dst> operator*(const gfx::SizeTyped<Src>& aSize,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::SizeTyped<Dst>(aSize.width * aScale.scale,
+ aSize.height * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::SizeTyped<Dst> operator/(const gfx::SizeTyped<Src>& aSize,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::SizeTyped<Dst>(aSize.width / aScale.scale,
+ aSize.height / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::SizeTyped<Dst, F> operator*(
+ const gfx::SizeTyped<Src, F>& aSize,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::SizeTyped<Dst, F>(aSize.width * aScale.xScale,
+ aSize.height * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::SizeTyped<Dst, F> operator/(
+ const gfx::SizeTyped<Src, F>& aSize,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::SizeTyped<Dst, F>(aSize.width / aScale.xScale,
+ aSize.height / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::SizeTyped<Dst> operator*(const gfx::IntSizeTyped<Src>& aSize,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::SizeTyped<Dst>(float(aSize.width) * aScale.scale,
+ float(aSize.height) * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::SizeTyped<Dst> operator/(const gfx::IntSizeTyped<Src>& aSize,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::SizeTyped<Dst>(float(aSize.width) / aScale.scale,
+ float(aSize.height) / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::SizeTyped<Dst, F> operator*(
+ const gfx::IntSizeTyped<Src>& aSize,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::SizeTyped<Dst, F>(F(aSize.width) * aScale.xScale,
+ F(aSize.height) * aScale.yScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::SizeTyped<Dst, F> operator/(
+ const gfx::IntSizeTyped<Src>& aSize,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::SizeTyped<Dst, F>(F(aSize.width) / aScale.xScale,
+ F(aSize.height) / aScale.yScale);
+}
+
+template <class Src, class Dst>
+gfx::MarginTyped<Dst> operator*(const gfx::MarginTyped<Src>& aMargin,
+ const gfx::ScaleFactor<Src, Dst>& aScale) {
+ return gfx::MarginTyped<Dst>(
+ aMargin.top.value * aScale.scale, aMargin.right.value * aScale.scale,
+ aMargin.bottom.value * aScale.scale, aMargin.left.value * aScale.scale);
+}
+
+template <class Src, class Dst>
+gfx::MarginTyped<Dst> operator/(const gfx::MarginTyped<Src>& aMargin,
+ const gfx::ScaleFactor<Dst, Src>& aScale) {
+ return gfx::MarginTyped<Dst>(
+ aMargin.top / aScale.scale, aMargin.right / aScale.scale,
+ aMargin.bottom / aScale.scale, aMargin.left / aScale.scale);
+}
+
+template <class Src, class Dst, class F>
+gfx::MarginTyped<Dst, F> operator*(
+ const gfx::MarginTyped<Src, F>& aMargin,
+ const gfx::BaseScaleFactors2D<Src, Dst, F>& aScale) {
+ return gfx::MarginTyped<Dst, F>(
+ aMargin.top.value * aScale.yScale, aMargin.right.value * aScale.xScale,
+ aMargin.bottom.value * aScale.yScale, aMargin.left.value * aScale.xScale);
+}
+
+template <class Src, class Dst, class F>
+gfx::MarginTyped<Dst, F> operator/(
+ const gfx::MarginTyped<Src, F>& aMargin,
+ const gfx::BaseScaleFactors2D<Dst, Src, F>& aScale) {
+ return gfx::MarginTyped<Dst, F>(
+ aMargin.top.value / aScale.yScale, aMargin.right.value / aScale.xScale,
+ aMargin.bottom.value / aScale.yScale, aMargin.left.value / aScale.xScale);
+}
+
+// Calculate the max or min or the ratios of the widths and heights of two
+// sizes, returning a scale factor in the correct units.
+
+template <class Src, class Dst>
+gfx::ScaleFactor<Src, Dst> MaxScaleRatio(const gfx::SizeTyped<Dst>& aDestSize,
+ const gfx::SizeTyped<Src>& aSrcSize) {
+ MOZ_ASSERT(aSrcSize.width != 0 && aSrcSize.height != 0,
+ "Caller must verify aSrcSize has nonzero components, "
+ "to avoid division by 0 here");
+ return gfx::ScaleFactor<Src, Dst>(std::max(
+ aDestSize.width / aSrcSize.width, aDestSize.height / aSrcSize.height));
+}
+
+template <class Src, class Dst>
+gfx::ScaleFactor<Src, Dst> MinScaleRatio(const gfx::SizeTyped<Dst>& aDestSize,
+ const gfx::SizeTyped<Src>& aSrcSize) {
+ MOZ_ASSERT(aSrcSize.width != 0 && aSrcSize.height != 0,
+ "Caller must verify aSrcSize has nonzero components, "
+ "to avoid division by 0 here");
+ return gfx::ScaleFactor<Src, Dst>(std::min(
+ aDestSize.width / aSrcSize.width, aDestSize.height / aSrcSize.height));
+}
+
+template <typename T>
+struct CoordOfImpl;
+
+template <typename Units>
+struct CoordOfImpl<gfx::PointTyped<Units>> {
+ typedef gfx::CoordTyped<Units> Type;
+};
+
+template <typename Units>
+struct CoordOfImpl<gfx::IntPointTyped<Units>> {
+ typedef gfx::IntCoordTyped<Units> Type;
+};
+
+template <typename Units>
+struct CoordOfImpl<gfx::RectTyped<Units>> {
+ typedef gfx::CoordTyped<Units> Type;
+};
+
+template <typename Units>
+struct CoordOfImpl<gfx::IntRectTyped<Units>> {
+ typedef gfx::IntCoordTyped<Units> Type;
+};
+
+template <typename Units>
+struct CoordOfImpl<gfx::SizeTyped<Units>> {
+ typedef gfx::CoordTyped<Units> Type;
+};
+
+template <typename T>
+using CoordOf = typename CoordOfImpl<T>::Type;
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ViewportUtils.cpp b/layout/base/ViewportUtils.cpp
new file mode 100644
index 0000000000..f0d18510bf
--- /dev/null
+++ b/layout/base/ViewportUtils.cpp
@@ -0,0 +1,296 @@
+/* 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 "Units.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ViewportFrame.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/InputAPZContext.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsQueryFrame.h"
+#include "nsStyleStruct.h"
+
+namespace mozilla {
+
+using layers::APZCCallbackHelper;
+using layers::InputAPZContext;
+using layers::ScrollableLayerGuid;
+
+template <typename Units>
+gfx::Matrix4x4Typed<Units, Units> ViewportUtils::GetVisualToLayoutTransform(
+ ScrollableLayerGuid::ViewID aScrollId) {
+ static_assert(
+ std::is_same_v<Units, CSSPixel> ||
+ std::is_same_v<Units, LayoutDevicePixel>,
+ "GetCallbackTransform() may only be used with CSS or LayoutDevice units");
+
+ if (aScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
+ return {};
+ }
+ nsCOMPtr<nsIContent> content = nsLayoutUtils::FindContentFor(aScrollId);
+ if (!content || !content->GetPrimaryFrame()) {
+ return {};
+ }
+
+ // First, scale inversely by the root content document's pres shell
+ // resolution to cancel the scale-to-resolution transform that the
+ // compositor adds to the layer with the pres shell resolution. The points
+ // sent to Gecko by APZ don't have this transform unapplied (unlike other
+ // compositor-side transforms) because Gecko needs it applied when hit
+ // testing against content that's conceptually outside the resolution,
+ // such as scrollbars.
+ float resolution = 1.0f;
+ if (PresShell* presShell =
+ APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
+ content)) {
+ resolution = presShell->GetResolution();
+ }
+
+ // Now apply the callback-transform. This is only approximately correct,
+ // see the comment on GetCumulativeApzCallbackTransform for details.
+ gfx::PointTyped<Units> transform;
+ CSSPoint transformCSS = nsLayoutUtils::GetCumulativeApzCallbackTransform(
+ content->GetPrimaryFrame());
+ if constexpr (std::is_same_v<Units, CSSPixel>) {
+ transform = transformCSS;
+ } else { // Units == LayoutDevicePixel
+ transform = transformCSS *
+ content->GetPrimaryFrame()->PresContext()->CSSToDevPixelScale();
+ }
+
+ return gfx::Matrix4x4Typed<Units, Units>::Scaling(1 / resolution,
+ 1 / resolution, 1)
+ .PostTranslate(transform.x, transform.y, 0);
+}
+
+CSSToCSSMatrix4x4 GetVisualToLayoutTransform(PresShell* aContext) {
+ ScrollableLayerGuid::ViewID targetScrollId =
+ InputAPZContext::GetTargetLayerGuid().mScrollId;
+ if (targetScrollId == ScrollableLayerGuid::NULL_SCROLL_ID) {
+ if (nsIFrame* rootScrollFrame = aContext->GetRootScrollFrame()) {
+ targetScrollId =
+ nsLayoutUtils::FindOrCreateIDFor(rootScrollFrame->GetContent());
+ }
+ }
+ return ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
+}
+
+nsPoint ViewportUtils::VisualToLayout(const nsPoint& aPt, PresShell* aContext) {
+ auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
+ CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
+ cssPt = visualToLayout.TransformPoint(cssPt);
+ return CSSPoint::ToAppUnits(cssPt);
+}
+
+nsRect ViewportUtils::VisualToLayout(const nsRect& aRect, PresShell* aContext) {
+ auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
+ CSSRect cssRect = CSSRect::FromAppUnits(aRect);
+ cssRect = visualToLayout.TransformBounds(cssRect);
+ nsRect result = CSSRect::ToAppUnits(cssRect);
+
+ // In hit testing codepaths, the input rect often has dimensions of one app
+ // units. If we are zoomed in enough, the rounded size of the output rect
+ // can be zero app units, which will fail to Intersect() with anything, and
+ // therefore cause hit testing to fail. To avoid this, we expand the output
+ // rect to one app units.
+ if (!aRect.IsEmpty() && result.IsEmpty()) {
+ result.width = 1;
+ result.height = 1;
+ }
+
+ return result;
+}
+
+nsPoint ViewportUtils::LayoutToVisual(const nsPoint& aPt, PresShell* aContext) {
+ auto visualToLayout = mozilla::GetVisualToLayoutTransform(aContext);
+ CSSPoint cssPt = CSSPoint::FromAppUnits(aPt);
+ auto transformed = visualToLayout.Inverse().TransformPoint(cssPt);
+ return CSSPoint::ToAppUnits(transformed);
+}
+
+LayoutDevicePoint ViewportUtils::DocumentRelativeLayoutToVisual(
+ const LayoutDevicePoint& aPoint, PresShell* aShell) {
+ ScrollableLayerGuid::ViewID targetScrollId =
+ nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
+ auto visualToLayout =
+ ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
+ targetScrollId);
+ return visualToLayout.Inverse().TransformPoint(aPoint);
+}
+
+LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
+ const LayoutDeviceRect& aRect, PresShell* aShell) {
+ ScrollableLayerGuid::ViewID targetScrollId =
+ nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
+ auto visualToLayout =
+ ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
+ targetScrollId);
+ return visualToLayout.Inverse().TransformBounds(aRect);
+}
+
+LayoutDeviceRect ViewportUtils::DocumentRelativeLayoutToVisual(
+ const LayoutDeviceIntRect& aRect, PresShell* aShell) {
+ return DocumentRelativeLayoutToVisual(IntRectToRect(aRect), aShell);
+}
+
+CSSRect ViewportUtils::DocumentRelativeLayoutToVisual(const CSSRect& aRect,
+ PresShell* aShell) {
+ ScrollableLayerGuid::ViewID targetScrollId =
+ nsLayoutUtils::ScrollIdForRootScrollFrame(aShell->GetPresContext());
+ auto visualToLayout =
+ ViewportUtils::GetVisualToLayoutTransform(targetScrollId);
+ return visualToLayout.Inverse().TransformBounds(aRect);
+}
+
+template <class SourceUnits, class DestUnits>
+gfx::PointTyped<DestUnits> TransformPointOrRect(
+ const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
+ const gfx::PointTyped<SourceUnits>& aPoint) {
+ return aMatrix.TransformPoint(aPoint);
+}
+
+template <class SourceUnits, class DestUnits>
+gfx::RectTyped<DestUnits> TransformPointOrRect(
+ const gfx::Matrix4x4Typed<SourceUnits, DestUnits>& aMatrix,
+ const gfx::RectTyped<SourceUnits>& aRect) {
+ return aMatrix.TransformBounds(aRect);
+}
+
+template <class LDPointOrRect>
+LDPointOrRect ConvertToScreenRelativeVisual(const LDPointOrRect& aInput,
+ nsPresContext* aCtx) {
+ MOZ_ASSERT(aCtx);
+
+ LDPointOrRect layoutToVisual(aInput);
+ nsIFrame* prevRootFrame = nullptr;
+ nsPresContext* prevCtx = nullptr;
+
+ // Walk up to the rootmost prescontext, transforming as we go.
+ for (nsPresContext* ctx = aCtx; ctx; ctx = ctx->GetParentPresContext()) {
+ PresShell* shell = ctx->PresShell();
+ nsIFrame* rootFrame = shell->GetRootFrame();
+ if (prevRootFrame) {
+ // Convert layoutToVisual from being relative to `prevRootFrame`
+ // to being relative to `rootFrame` (layout space).
+ nscoord apd = prevCtx->AppUnitsPerDevPixel();
+ nsPoint offset = prevRootFrame->GetOffsetToCrossDoc(rootFrame, apd);
+ layoutToVisual += LayoutDevicePoint::FromAppUnits(offset, apd);
+ }
+ if (shell->GetResolution() != 1.0) {
+ // Found the APZ zoom root, so do the layout -> visual conversion.
+ layoutToVisual =
+ ViewportUtils::DocumentRelativeLayoutToVisual(layoutToVisual, shell);
+ }
+
+ prevRootFrame = rootFrame;
+ prevCtx = ctx;
+ }
+
+ // If we're in a nested content process, the above traversal will not have
+ // encountered the APZ zoom root. The translation part of the layout-to-visual
+ // transform will be included in |rootScreenRect.TopLeft()|, added below
+ // (that ultimately comes from nsIWidget::WidgetToScreenOffset(), which for an
+ // OOP iframe's widget includes this translation), but the scale part needs to
+ // be computed and added separately.
+ Scale2D enclosingResolution =
+ ViewportUtils::TryInferEnclosingResolution(prevCtx->GetPresShell());
+ if (enclosingResolution != Scale2D{1.0f, 1.0f}) {
+ layoutToVisual = TransformPointOrRect(
+ LayoutDeviceToLayoutDeviceMatrix4x4::Scaling(
+ enclosingResolution.xScale, enclosingResolution.yScale, 1.0f),
+ layoutToVisual);
+ }
+
+ // Then we do the conversion from the rootmost presContext's root frame (in
+ // visual space) to screen space.
+ LayoutDeviceIntRect rootScreenRect =
+ LayoutDeviceIntRect::FromAppUnitsToNearest(
+ prevRootFrame->GetScreenRectInAppUnits(),
+ prevCtx->AppUnitsPerDevPixel());
+
+ return layoutToVisual + rootScreenRect.TopLeft();
+}
+
+LayoutDevicePoint ViewportUtils::ToScreenRelativeVisual(
+ const LayoutDevicePoint& aPt, nsPresContext* aCtx) {
+ return ConvertToScreenRelativeVisual(aPt, aCtx);
+}
+
+LayoutDeviceRect ViewportUtils::ToScreenRelativeVisual(
+ const LayoutDeviceRect& aRect, nsPresContext* aCtx) {
+ return ConvertToScreenRelativeVisual(aRect, aCtx);
+}
+
+// Definitions of the two explicit instantiations forward declared in the header
+// file. This causes code for these instantiations to be emitted into the object
+// file for ViewportUtils.cpp.
+template CSSToCSSMatrix4x4 ViewportUtils::GetVisualToLayoutTransform<CSSPixel>(
+ ScrollableLayerGuid::ViewID);
+template LayoutDeviceToLayoutDeviceMatrix4x4
+ ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
+ ScrollableLayerGuid::ViewID);
+
+const nsIFrame* ViewportUtils::IsZoomedContentRoot(const nsIFrame* aFrame) {
+ if (!aFrame) {
+ return nullptr;
+ }
+ if (aFrame->Type() == LayoutFrameType::Canvas ||
+ aFrame->Type() == LayoutFrameType::PageSequence) {
+ nsIScrollableFrame* sf = do_QueryFrame(aFrame->GetParent());
+ if (sf && sf->IsRootScrollFrameOfDocument() &&
+ aFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ return aFrame->GetParent();
+ }
+ } else if (aFrame->StyleDisplay()->mPosition ==
+ StylePositionProperty::Fixed) {
+ if (ViewportFrame* viewportFrame = do_QueryFrame(aFrame->GetParent())) {
+ if (viewportFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ return viewportFrame->PresShell()->GetRootScrollFrame();
+ }
+ }
+ }
+ return nullptr;
+}
+
+Scale2D ViewportUtils::TryInferEnclosingResolution(PresShell* aShell) {
+ MOZ_ASSERT(aShell && aShell->GetPresContext());
+ MOZ_ASSERT(!aShell->GetPresContext()->GetParentPresContext(),
+ "TryInferEnclosingResolution can only be called for a root pres "
+ "shell within a process");
+ if (dom::BrowserChild* bc = dom::BrowserChild::GetFrom(aShell)) {
+ if (!bc->IsTopLevel()) {
+ // The enclosing resolution is not directly available in the BrowserChild.
+ // The closest thing available is GetChildToParentConversionMatrix(),
+ // which also includes any enclosing CSS transforms.
+ // The behaviour implemented here will not provide an accurate answer
+ // in the presence of CSS transforms, but it tries to do something
+ // reasonable:
+ // - If there are no enclosing CSS transforms, it will return the
+ // resolution.
+ // - If the enclosing transforms contain scales and translations only,
+ // it will return the resolution times the CSS transform scale
+ // (choosing the x-scale if they are different).
+ // - Otherwise, it will return the resolution times a scale component
+ // of the transform as returned by Matrix4x4Typed::Decompose().
+ // - If the enclosing transform is sufficiently complex that
+ // Decompose() returns false, give up and return 1.0.
+ gfx::Point3DTyped<gfx::UnknownUnits> translation;
+ gfx::Quaternion rotation;
+ gfx::Point3DTyped<gfx::UnknownUnits> scale;
+ if (bc->GetChildToParentConversionMatrix().Decompose(translation,
+ rotation, scale)) {
+ return {scale.x, scale.y};
+ }
+ }
+ }
+ return {1.0f, 1.0f};
+}
+
+} // namespace mozilla
diff --git a/layout/base/ViewportUtils.h b/layout/base/ViewportUtils.h
new file mode 100644
index 0000000000..bc65119f29
--- /dev/null
+++ b/layout/base/ViewportUtils.h
@@ -0,0 +1,128 @@
+/* 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_ViewportUtils_h
+#define mozilla_ViewportUtils_h
+
+#include "Units.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+
+class nsIFrame;
+class nsPresContext;
+
+namespace mozilla {
+
+class PresShell;
+
+class ViewportUtils {
+ public:
+ /* Return a transform to be applied to the coordinates of input events
+ targeting content inside the scroll frame identified by |aScrollId|, which
+ converts from "visual coordinates" (which are the coordinates events have
+ when they arrive from APZ) to "layout coordinates" (which are the
+ coordinates used in most places by layout code). The transform has two
+ components:
+ 1. The pres shell resolution, representing the pinch-zoom scale
+ (if the scroll frame |aScrollId| is inside the resolution, which
+ is most of the time).
+ 2. A translation representing async scrolling. This can contain:
+ - For any scroll frame, a scroll component resulting from the main
+ thread incompletely applying an APZ-requested scroll position.
+ - For the RCD-RSF only, a persistent component representing the
+ offset of the visual viewport relative to the layout viewport.
+ The translation is accumulated for all scroll frames form |aScrollId|
+ up to the root, using values populated in
+ APZCCallbackHelper::UpdateCallbackTransform. See that method's
+ documentation for additional details. */
+ template <typename Units = CSSPixel>
+ static gfx::Matrix4x4Typed<Units, Units> GetVisualToLayoutTransform(
+ layers::ScrollableLayerGuid::ViewID aScrollId);
+
+ /* The functions below apply GetVisualToLayoutTransform() or its inverse
+ * to various quantities.
+ *
+ * To determine the appropriate scroll id to pass into
+ * GetVisualToLayoutTransform(), these functions prefer to use the one
+ * from InputAPZContext::GetTargetLayerGuid() if one is available.
+ * If one is not available, they use the scroll id of the root scroll
+ * frame of the pres shell passed in as |aContext| as a fallback.
+ */
+ static nsPoint VisualToLayout(const nsPoint& aPt, PresShell* aContext);
+ static nsRect VisualToLayout(const nsRect& aRect, PresShell* aContext);
+ static nsPoint LayoutToVisual(const nsPoint& aPt, PresShell* aContext);
+
+ /*
+ * These functions convert the point/rect from layout to visual space
+ * by applying the inverse of GetLayoutToVisualTransform() of the root
+ * scrollframe of provided presShell.
+ */
+ static LayoutDevicePoint DocumentRelativeLayoutToVisual(
+ const LayoutDevicePoint& aPoint, PresShell* aShell);
+ static LayoutDeviceRect DocumentRelativeLayoutToVisual(
+ const LayoutDeviceRect& aRect, PresShell* aShell);
+ static LayoutDeviceRect DocumentRelativeLayoutToVisual(
+ const LayoutDeviceIntRect& aRect, PresShell* aShell);
+ static CSSRect DocumentRelativeLayoutToVisual(const CSSRect& aRect,
+ PresShell* aShell);
+
+ /*
+ * These convert aPt/aRect, which is the layout space of aCtx, into a
+ * screen-relative visual-space quantity. Formally, we can think of the input
+ * as being in RelativeTo{aCtx->PresShell()->GetRootFrame(),
+ * ViewportType::Layout} space. And then the function iterates up the chain of
+ * presContexts, transforming the point along, until it gets to the
+ * RelativeTo{aCtx->GetRootPresContext()->PresShell()->GetRootFrame(),
+ * ViewportType::Visual} space. Then it further transforms it into a
+ * screen-relative space. Unlike the GetTransformToAncestor family of helper
+ * methods, this transformation does NOT include CSS transforms. Also, unlike
+ * other ViewportUtils methods, the input is relative to `aCtx` which might be
+ * a sub-shell of the presShell with the resolution.
+ */
+ static LayoutDevicePoint ToScreenRelativeVisual(const LayoutDevicePoint& aPt,
+ nsPresContext* aCtx);
+ static LayoutDeviceRect ToScreenRelativeVisual(const LayoutDeviceRect& aRect,
+ nsPresContext* aCtx);
+
+ /**
+ * Returns non-null if |aFrame| is inside the async zoom container but its
+ * parent frame is not, thereby making |aFrame| a root of a subtree of
+ * frames representing content that is zoomed in. The value returned in such
+ * cases is the root scroll frame inside the async zoom container.
+
+ * Callers use this to identify points during frame tree traversal where the
+ * visual-to-layout transform needs to be applied.
+ */
+ static const nsIFrame* IsZoomedContentRoot(const nsIFrame* aFrame);
+
+ /**
+ * If |aShell| is in a nested content process, try to infer the resolution
+ * at which the enclosing root content
+ * document has been painted.
+ *
+ * Otherwise (if |aShell| is in the chrome or top-level content process),
+ * this function returns 1.0.
+ *
+ * |aShell| must be the root pres shell in its process.
+ *
+ * This function may not return an accurate answer if there is also
+ * a CSS transform enclosing the iframe.
+ */
+ static Scale2D TryInferEnclosingResolution(PresShell* aShell);
+};
+
+// Forward declare explicit instantiations of GetVisualToLayoutTransform() for
+// CSSPixel and LayoutDevicePixel, the only two types it gets used with.
+// These declarations promise to callers in any translation unit that _some_
+// translation unit (in this case, ViewportUtils.cpp) will contain the
+// definitions of these instantiations. This allows us to keep the definition
+// out-of-line in the source.
+extern template CSSToCSSMatrix4x4 ViewportUtils::GetVisualToLayoutTransform<
+ CSSPixel>(layers::ScrollableLayerGuid::ViewID);
+extern template LayoutDeviceToLayoutDeviceMatrix4x4
+ ViewportUtils::GetVisualToLayoutTransform<LayoutDevicePixel>(
+ layers::ScrollableLayerGuid::ViewID);
+
+} // namespace mozilla
+
+#endif /* mozilla_ViewportUtils_h */
diff --git a/layout/base/WordMovementType.h b/layout/base/WordMovementType.h
new file mode 100644
index 0000000000..65d51542da
--- /dev/null
+++ b/layout/base/WordMovementType.h
@@ -0,0 +1,16 @@
+/* -*- 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 WordMovementType_h___
+#define WordMovementType_h___
+
+namespace mozilla {
+
+enum EWordMovementType { eStartWord, eEndWord, eDefaultBehavior };
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/ZoomConstraintsClient.cpp b/layout/base/ZoomConstraintsClient.cpp
new file mode 100644
index 0000000000..e695c7755e
--- /dev/null
+++ b/layout/base/ZoomConstraintsClient.cpp
@@ -0,0 +1,281 @@
+/* -*- 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 "ZoomConstraintsClient.h"
+
+#include <inttypes.h>
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/layers/ZoomConstraints.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/Event.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsLayoutUtils.h"
+#include "nsPoint.h"
+#include "nsView.h"
+#include "nsViewportInfo.h"
+#include "Units.h"
+#include "UnitTransforms.h"
+
+static mozilla::LazyLogModule sApzZoomLog("apz.zoom");
+#define ZCC_LOG(...) MOZ_LOG(sApzZoomLog, LogLevel::Debug, (__VA_ARGS__))
+
+NS_IMPL_ISUPPORTS(ZoomConstraintsClient, nsIDOMEventListener, nsIObserver)
+
+#define DOM_META_ADDED u"DOMMetaAdded"_ns
+#define DOM_META_CHANGED u"DOMMetaChanged"_ns
+#define FULLSCREEN_CHANGED u"fullscreenchange"_ns
+#define BEFORE_FIRST_PAINT "before-first-paint"_ns
+#define COMPOSITOR_REINITIALIZED "compositor-reinitialized"_ns
+#define NS_PREF_CHANGED "nsPref:changed"_ns
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::layers;
+
+ZoomConstraintsClient::ZoomConstraintsClient()
+ : mDocument(nullptr),
+ mPresShell(nullptr),
+ mZoomConstraints(false, false, CSSToParentLayerScale(1.f),
+ CSSToParentLayerScale(1.f)) {}
+
+ZoomConstraintsClient::~ZoomConstraintsClient() = default;
+
+static nsIWidget* GetWidget(PresShell* aPresShell) {
+ if (!aPresShell) {
+ return nullptr;
+ }
+ if (nsIFrame* rootFrame = aPresShell->GetRootFrame()) {
+ if (nsView* view = rootFrame->GetView()) {
+ return view->GetWidget();
+ }
+ }
+ return nullptr;
+}
+
+void ZoomConstraintsClient::Destroy() {
+ if (!(mPresShell && mDocument)) {
+ return;
+ }
+
+ ZCC_LOG("Destroying %p\n", this);
+
+ if (mEventTarget) {
+ mEventTarget->RemoveEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->RemoveEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->RemoveSystemEventListener(FULLSCREEN_CHANGED, this, false);
+ mEventTarget = nullptr;
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->RemoveObserver(this, BEFORE_FIRST_PAINT.Data());
+ observerService->RemoveObserver(this, COMPOSITOR_REINITIALIZED.Data());
+ }
+
+ Preferences::RemoveObserver(this, "browser.ui.zoom.force-user-scalable");
+
+ if (mGuid) {
+ if (nsIWidget* widget = GetWidget(mPresShell)) {
+ ZCC_LOG("Sending null constraints in %p for { %u, %" PRIu64 " }\n", this,
+ mGuid->mPresShellId, mGuid->mScrollId);
+ widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId,
+ Nothing());
+ mGuid = Nothing();
+ }
+ }
+
+ mDocument = nullptr;
+ mPresShell = nullptr;
+}
+
+void ZoomConstraintsClient::Init(PresShell* aPresShell, Document* aDocument) {
+ if (!(aPresShell && aDocument)) {
+ return;
+ }
+
+ mPresShell = aPresShell;
+ mDocument = aDocument;
+
+ if (nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow()) {
+ mEventTarget = window->GetParentTarget();
+ }
+ if (mEventTarget) {
+ mEventTarget->AddEventListener(DOM_META_ADDED, this, false);
+ mEventTarget->AddEventListener(DOM_META_CHANGED, this, false);
+ mEventTarget->AddSystemEventListener(FULLSCREEN_CHANGED, this, false);
+ }
+
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->AddObserver(this, BEFORE_FIRST_PAINT.Data(), false);
+ observerService->AddObserver(this, COMPOSITOR_REINITIALIZED.Data(), false);
+ }
+
+ Preferences::AddStrongObserver(this, "browser.ui.zoom.force-user-scalable");
+}
+
+NS_IMETHODIMP
+ZoomConstraintsClient::HandleEvent(dom::Event* event) {
+ nsAutoString type;
+ event->GetType(type);
+
+ if (type.Equals(DOM_META_ADDED)) {
+ ZCC_LOG("Got a dom-meta-added event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (type.Equals(DOM_META_CHANGED)) {
+ ZCC_LOG("Got a dom-meta-changed event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (type.Equals(FULLSCREEN_CHANGED)) {
+ ZCC_LOG("Got a fullscreen-change event in %p\n", this);
+ RefreshZoomConstraints();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+ZoomConstraintsClient::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (SameCOMIdentity(aSubject, ToSupports(mDocument)) &&
+ BEFORE_FIRST_PAINT.EqualsASCII(aTopic)) {
+ ZCC_LOG("Got a before-first-paint event in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (COMPOSITOR_REINITIALIZED.EqualsASCII(aTopic)) {
+ ZCC_LOG("Got a compositor-reinitialized notification in %p\n", this);
+ RefreshZoomConstraints();
+ } else if (NS_PREF_CHANGED.EqualsASCII(aTopic)) {
+ ZCC_LOG("Got a pref-change event in %p\n", this);
+ // We need to run this later because all the pref change listeners need
+ // to execute before we can be guaranteed that
+ // StaticPrefs::browser_ui_zoom_force_user_scalable() returns the updated
+ // value.
+
+ RefPtr<nsRunnableMethod<ZoomConstraintsClient>> event =
+ NewRunnableMethod("ZoomConstraintsClient::RefreshZoomConstraints", this,
+ &ZoomConstraintsClient::RefreshZoomConstraints);
+ mDocument->Dispatch(event.forget());
+ }
+ return NS_OK;
+}
+
+void ZoomConstraintsClient::ScreenSizeChanged() {
+ ZCC_LOG("Got a screen-size change notification in %p\n", this);
+ RefreshZoomConstraints();
+}
+
+static mozilla::layers::ZoomConstraints ComputeZoomConstraintsFromViewportInfo(
+ const nsViewportInfo& aViewportInfo, Document* aDocument) {
+ mozilla::layers::ZoomConstraints constraints;
+ constraints.mAllowZoom = aViewportInfo.IsZoomAllowed() &&
+ nsLayoutUtils::AllowZoomingForDocument(aDocument);
+ constraints.mAllowDoubleTapZoom =
+ constraints.mAllowZoom && StaticPrefs::apz_allow_double_tap_zooming();
+ if (constraints.mAllowZoom) {
+ constraints.mMinZoom.scale = aViewportInfo.GetMinZoom().scale;
+ constraints.mMaxZoom.scale = aViewportInfo.GetMaxZoom().scale;
+ } else {
+ constraints.mMinZoom.scale = aViewportInfo.GetDefaultZoom().scale;
+ constraints.mMaxZoom.scale = aViewportInfo.GetDefaultZoom().scale;
+ }
+ return constraints;
+}
+
+void ZoomConstraintsClient::RefreshZoomConstraints() {
+ mZoomConstraints = ZoomConstraints(false, false, CSSToParentLayerScale(1.f),
+ CSSToParentLayerScale(1.f));
+
+ nsIWidget* widget = GetWidget(mPresShell);
+ if (!widget) {
+ return;
+ }
+
+ uint32_t presShellId = 0;
+ ScrollableLayerGuid::ViewID viewId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ bool scrollIdentifiersValid =
+ APZCCallbackHelper::GetOrCreateScrollIdentifiers(
+ mDocument->GetDocumentElement(), &presShellId, &viewId);
+ if (!scrollIdentifiersValid) {
+ return;
+ }
+
+ LayoutDeviceIntSize screenSize;
+ if (!nsLayoutUtils::GetDocumentViewerSize(mPresShell->GetPresContext(),
+ screenSize)) {
+ return;
+ }
+
+ nsViewportInfo viewportInfo = mDocument->GetViewportInfo(ViewAs<ScreenPixel>(
+ screenSize, PixelCastJustification::LayoutDeviceIsScreenForBounds));
+
+ mZoomConstraints =
+ ComputeZoomConstraintsFromViewportInfo(viewportInfo, mDocument);
+
+ if (mDocument->Fullscreen()) {
+ ZCC_LOG("%p is in fullscreen, disallowing zooming\n", this);
+ mZoomConstraints.mAllowZoom = false;
+ mZoomConstraints.mAllowDoubleTapZoom = false;
+ }
+
+ if (mDocument->IsStaticDocument()) {
+ ZCC_LOG("%p is in print or print preview, disallowing double tap zooming\n",
+ this);
+ mZoomConstraints.mAllowDoubleTapZoom = false;
+ }
+
+ if (nsContentUtils::IsPDFJS(mDocument->GetPrincipal())) {
+ ZCC_LOG("%p is pdf.js viewer, disallowing double tap zooming\n", this);
+ mZoomConstraints.mAllowDoubleTapZoom = false;
+ }
+
+ // On macOS the OS can send us a double tap zoom event from the touchpad and
+ // there are no touch screen macOS devices so we never wait to see if a second
+ // tap is coming so we can always allow double tap zooming on mac. We need
+ // this because otherwise the width check usually disables it.
+ bool allow_double_tap_always = false;
+#ifdef XP_MACOSX
+ allow_double_tap_always =
+ StaticPrefs::apz_mac_enable_double_tap_zoom_touchpad_gesture();
+#endif
+ if (!allow_double_tap_always && mZoomConstraints.mAllowDoubleTapZoom) {
+ // If the CSS viewport is narrower than the screen (i.e. width <=
+ // device-width) then we disable double-tap-to-zoom behaviour.
+ CSSToLayoutDeviceScale scale =
+ mPresShell->GetPresContext()->CSSToDevPixelScale();
+ if ((viewportInfo.GetSize() * scale).width <= screenSize.width) {
+ mZoomConstraints.mAllowDoubleTapZoom = false;
+ }
+ }
+
+ // We only ever create a ZoomConstraintsClient for an RCD, so the RSF of
+ // the presShell must be the RCD-RSF (if it exists).
+ MOZ_ASSERT(mPresShell->GetPresContext()->IsRootContentDocumentCrossProcess());
+ if (nsIScrollableFrame* rcdrsf =
+ mPresShell->GetRootScrollFrameAsScrollable()) {
+ ZCC_LOG("Notifying RCD-RSF that it is zoomable: %d\n",
+ mZoomConstraints.mAllowZoom);
+ rcdrsf->SetZoomableByAPZ(mZoomConstraints.mAllowZoom);
+ }
+
+ ScrollableLayerGuid newGuid(LayersId{0}, presShellId, viewId);
+ if (mGuid && mGuid.value() != newGuid) {
+ ZCC_LOG("Clearing old constraints in %p for { %u, %" PRIu64 " }\n", this,
+ mGuid->mPresShellId, mGuid->mScrollId);
+ // If the guid changes, send a message to clear the old one
+ widget->UpdateZoomConstraints(mGuid->mPresShellId, mGuid->mScrollId,
+ Nothing());
+ }
+ mGuid = Some(newGuid);
+ ZCC_LOG("Sending constraints %s in %p for { %u, %" PRIu64 " }\n",
+ ToString(mZoomConstraints).c_str(), this, presShellId, viewId);
+ widget->UpdateZoomConstraints(presShellId, viewId, Some(mZoomConstraints));
+}
diff --git a/layout/base/ZoomConstraintsClient.h b/layout/base/ZoomConstraintsClient.h
new file mode 100644
index 0000000000..3f80ca7558
--- /dev/null
+++ b/layout/base/ZoomConstraintsClient.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/. */
+
+#ifndef ZoomConstraintsClient_h_
+#define ZoomConstraintsClient_h_
+
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/layers/ZoomConstraints.h"
+#include "mozilla/Maybe.h"
+#include "nsCOMPtr.h"
+#include "nsIDOMEventListener.h"
+#include "nsIObserver.h"
+
+namespace mozilla {
+class PresShell;
+namespace dom {
+class Document;
+class EventTarget;
+} // namespace dom
+} // namespace mozilla
+
+class ZoomConstraintsClient final : public nsIDOMEventListener,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+ NS_DECL_NSIOBSERVER
+
+ ZoomConstraintsClient();
+
+ private:
+ ~ZoomConstraintsClient();
+
+ public:
+ void Init(mozilla::PresShell* aPresShell, mozilla::dom::Document* aDocument);
+ void Destroy();
+ void ScreenSizeChanged();
+
+ bool GetAllowZoom() const { return mZoomConstraints.mAllowZoom; }
+
+ private:
+ void RefreshZoomConstraints();
+
+ RefPtr<mozilla::dom::Document> mDocument;
+ // raw ref since the presShell owns this
+ mozilla::PresShell* MOZ_NON_OWNING_REF mPresShell;
+ nsCOMPtr<mozilla::dom::EventTarget> mEventTarget;
+ mozilla::Maybe<mozilla::layers::ScrollableLayerGuid> mGuid;
+ mozilla::layers::ZoomConstraints mZoomConstraints;
+};
+
+#endif
diff --git a/layout/base/crashtests/1001237.html b/layout/base/crashtests/1001237.html
new file mode 100644
index 0000000000..fa7d2f6a61
--- /dev/null
+++ b/layout/base/crashtests/1001237.html
@@ -0,0 +1,10 @@
+<html>
+ <body>
+ <br id="x" style="transform-style: preserve-3d;">
+ <script>
+ document.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById("x").style.transform = "scale(2, 2)";
+ });
+ </script>
+ </body>
+</html>
diff --git a/layout/base/crashtests/1009036.html b/layout/base/crashtests/1009036.html
new file mode 100644
index 0000000000..f9f22e3bc4
--- /dev/null
+++ b/layout/base/crashtests/1009036.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom()
+{
+ var div = document.getElementsByTagName("div")[0];
+ div.childNodes[1].convertPointFromNode({x:0, y:0}, div.childNodes[0]);
+}
+</script>
+</head>
+<body onload="boom();">
+<div><span>&#x05D0;C</span> </div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1043163-1.html b/layout/base/crashtests/1043163-1.html
new file mode 100644
index 0000000000..d32b17a55e
--- /dev/null
+++ b/layout/base/crashtests/1043163-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE HTML>
+<html style="mask: url(#none);"><canvas style="transform: scaleY(-118055395520340);"></canvas></html>
diff --git a/layout/base/crashtests/1061028.html b/layout/base/crashtests/1061028.html
new file mode 100644
index 0000000000..98ea59f04f
--- /dev/null
+++ b/layout/base/crashtests/1061028.html
@@ -0,0 +1,9 @@
+<style>td:first-letter {
+</style>
+><table border=0>
+ <td><table id=test1>><td id=test2>
+<script>
+setTimeout("tCFcrash()", 41);
+function tCFcrash() {
+test1.appendChild(test2);
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/1107508-1.html b/layout/base/crashtests/1107508-1.html
new file mode 100644
index 0000000000..1ae6b1392d
--- /dev/null
+++ b/layout/base/crashtests/1107508-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<svg id="s">
+ <style>
+ #b { display: none; }
+ rect { fill:orange; }
+ </style>
+ <rect width="10" height="10" fill="lime"/>
+</svg>
+<style>
+ #b { display: block; }
+ rect { fill:blue; }
+</style>
+<div id="b" style="border:2px solid black">
+ <svg>
+ <use xlink:href="#s"/>
+ </svg>
+</div>
diff --git a/layout/base/crashtests/1116104.html b/layout/base/crashtests/1116104.html
new file mode 100644
index 0000000000..3f3f0169af
--- /dev/null
+++ b/layout/base/crashtests/1116104.html
@@ -0,0 +1,15 @@
+<html>
+
+<head>
+
+</head>
+
+<body>
+<style>colgroup::after { content:"after"; }</style>
+
+<table>
+<colgroup><col style="display: inline;">t</col></colgroup>
+</table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/1127198-1.html b/layout/base/crashtests/1127198-1.html
new file mode 100644
index 0000000000..8f1524050d
--- /dev/null
+++ b/layout/base/crashtests/1127198-1.html
@@ -0,0 +1,5 @@
+<style>.x { } .y { text-transform: uppercase; }</style><span id="I2">a<div id="I3">b</div></span><script>
+document.body.offsetTop;
+document.querySelector("span").className = "x";
+document.querySelector("div").className = "y";
+</script>
diff --git a/layout/base/crashtests/1140198.html b/layout/base/crashtests/1140198.html
new file mode 100644
index 0000000000..2e3f075b43
--- /dev/null
+++ b/layout/base/crashtests/1140198.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.style.display = "contents";
+ document.designMode = 'on';
+ document.documentElement.insertAdjacentHTML("beforeEnd", "<span><optgroup>");
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/1143535.html b/layout/base/crashtests/1143535.html
new file mode 100644
index 0000000000..774984c719
--- /dev/null
+++ b/layout/base/crashtests/1143535.html
@@ -0,0 +1,6 @@
+<style>
+ body::before {
+ display: ruby;
+ content: " ";
+ }
+</style>
diff --git a/layout/base/crashtests/1153716.html b/layout/base/crashtests/1153716.html
new file mode 100644
index 0000000000..6a2cd591e4
--- /dev/null
+++ b/layout/base/crashtests/1153716.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var td = document.createElement("td");
+ document.body.appendChild(td);
+ var audio = document.createElement("audio");
+ audio.controls = true;
+ td.appendChild(audio);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/1156588.html b/layout/base/crashtests/1156588.html
new file mode 100644
index 0000000000..b48bc3af25
--- /dev/null
+++ b/layout/base/crashtests/1156588.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("x").style.content = "'x'";
+ document.documentElement.offsetHeight;
+ document.getElementById("s").remove();
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="display: inline-grid; white-space: pre;"><div id="x"><span>
+<span>
+</span><span id="s"></span></span></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/1162813.xhtml b/layout/base/crashtests/1162813.xhtml
new file mode 100644
index 0000000000..2ff652e790
--- /dev/null
+++ b/layout/base/crashtests/1162813.xhtml
@@ -0,0 +1,17 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("l").value="פיל\n";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(boom, 30)}, 0);
+
+</script>
+ <hbox dir="rtl">
+ <label id="l" />
+ </hbox>
+</window>
diff --git a/layout/base/crashtests/1163583.html b/layout/base/crashtests/1163583.html
new file mode 100644
index 0000000000..d38e368d4d
--- /dev/null
+++ b/layout/base/crashtests/1163583.html
@@ -0,0 +1,14 @@
+</body>
+<script type="text/javascript">
+function convertArrayToStrings(array){array.forEach(function(value,index){array[index]=String.fromCharCode(value);}); return array};
+var test0=document.body.appendChild(document.createElement("frame"))
+var test1=document.body.appendChild(document.createElement("figure"))
+var test2=document.body.appendChild(document.createElement("details"))
+var test4=document.body.appendChild(document.createElement("embed"))
+
+for(x=0;x<6;x++){
+test0.appendChild(document.createTextNode(convertArrayToStrings([38010,20080,40959,29079,56831,13899,8295]).join('')))
+test4.appendChild(test0.cloneNode(true));
+}
+
+</script>
diff --git a/layout/base/crashtests/118931-1.html b/layout/base/crashtests/118931-1.html
new file mode 100644
index 0000000000..48a0bfa39f
--- /dev/null
+++ b/layout/base/crashtests/118931-1.html
@@ -0,0 +1,7 @@
+<BODY>
+
+<DIV id=container style="POSITION: absolute;"></DIV>
+
+<SCRIPT language=Javascript>
+ document.getElementById('container').style.position='relative';
+</SCRIPT> \ No newline at end of file
diff --git a/layout/base/crashtests/121533-1.html b/layout/base/crashtests/121533-1.html
new file mode 100644
index 0000000000..7cea9d659f
--- /dev/null
+++ b/layout/base/crashtests/121533-1.html
@@ -0,0 +1,11 @@
+<html>
+<title>B#121533</title>
+<script>function writeSorry() {document.writeln("test");
+document.close();
+}
+</script>
+
+<frameset cols="120,*" onLoad="writeSorry()">
+<frame name="topslider" src="#"> <frame name="bottomslider" src="#">
+</frameset>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/123049-1.html b/layout/base/crashtests/123049-1.html
new file mode 100644
index 0000000000..e4e51c58a7
--- /dev/null
+++ b/layout/base/crashtests/123049-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+<style>
+#myStyle:-moz-display-comboboxcontrol-frame {
+ -moz-user-input: none !important;
+}
+#myStyle:-moz-dropdown-list {
+ -moz-user-input: none !important;
+}
+</style>
+</head>
+<body onload="getElementById('mySelect').setAttribute('id', 'myStyle');"><select id="mySelect"></select></body></html>
diff --git a/layout/base/crashtests/1234622-1.html b/layout/base/crashtests/1234622-1.html
new file mode 100644
index 0000000000..20432a297f
--- /dev/null
+++ b/layout/base/crashtests/1234622-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+
+<script>
+
+window.addEventListener("load", function() {
+ setTimeout(function() {
+ window.location = "data:text/html,2";
+ }, 0);
+});
+
+window.addEventListener("pagehide", function() {
+ var x = document.createElement("object");
+ x.setAttribute("data", "data:text/plain,3");
+ document.documentElement.appendChild(x);
+});
+
+</script>
diff --git a/layout/base/crashtests/1235467-1.html b/layout/base/crashtests/1235467-1.html
new file mode 100644
index 0000000000..39a374b003
--- /dev/null
+++ b/layout/base/crashtests/1235467-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="transform: translateY(50%);">
+<div style="transform-style: preserve-3d; background-image: -moz-element(#a); position: sticky;" id="a">Q</div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/123946-1.html b/layout/base/crashtests/123946-1.html
new file mode 100644
index 0000000000..0ed86427c9
--- /dev/null
+++ b/layout/base/crashtests/123946-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+<title>test</title>
+</head>
+
+<body>
+<div id="test" style="position: absolute;">test</div>
+<script type="application/x-javascript">document.getElementById("test").style.position = "fixed";</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/1261351-iframe.html b/layout/base/crashtests/1261351-iframe.html
new file mode 100644
index 0000000000..43120a17b7
--- /dev/null
+++ b/layout/base/crashtests/1261351-iframe.html
@@ -0,0 +1,28 @@
+<body>
+<script type="application/javascript">
+ 'use strict';
+ // -sp-context: content
+ (function () {
+ class UiComponentTest extends HTMLDivElement {
+ constructor() {
+ super();
+ this.template = `<style></style>`;
+ }
+
+ connectedCallback() {
+ let shadow = this.attachShadow({ mode: "open" });
+ if (this.template) {
+ let te = document.createElement('template');
+ te.innerHTML = this.template;
+ shadow.appendChild(document.importNode(te.content, true));
+ }
+ }
+ };
+
+ customElements.define('ui-component-test', UiComponentTest, { extend: 'div'} );
+
+ let uic = new UiComponentTest();
+ document.body.appendChild(uic);
+ })();
+</script>
+</body>
diff --git a/layout/base/crashtests/1261351.html b/layout/base/crashtests/1261351.html
new file mode 100644
index 0000000000..70761652ee
--- /dev/null
+++ b/layout/base/crashtests/1261351.html
@@ -0,0 +1,7 @@
+<iframe id="iframe" src="1261351-iframe.html"></iframe>
+<script type="application/javascript">
+ let iframe = document.getElementById("iframe");
+ iframe.addEventListener("load", function() {
+ document.getElementsByTagName("iframe")[0].marginWidth = "5";
+ });
+</script>
diff --git a/layout/base/crashtests/1270797-1.html b/layout/base/crashtests/1270797-1.html
new file mode 100644
index 0000000000..8f9083c8c6
--- /dev/null
+++ b/layout/base/crashtests/1270797-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="isolation:isolate; background-blend-mode:darken; background-image: url(green100x100.jpg)">
+ <div style="mix-blend-mode: multiply; width: 200px; height: 200px; background-color:red"></div>
+</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/1270797-1.jpg b/layout/base/crashtests/1270797-1.jpg
new file mode 100644
index 0000000000..5b920f7c06
--- /dev/null
+++ b/layout/base/crashtests/1270797-1.jpg
Binary files differ
diff --git a/layout/base/crashtests/1278455-1.html b/layout/base/crashtests/1278455-1.html
new file mode 100644
index 0000000000..470fea730d
--- /dev/null
+++ b/layout/base/crashtests/1278455-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html style="transform: translateX(3px); display: grid;">
+<head>
+<!--
+user_pref("layout.event-regions.enabled", true);
+-->
+</head>
+<body>
+<div style="position: absolute;">Z</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1286889.html b/layout/base/crashtests/1286889.html
new file mode 100644
index 0000000000..b39d009d49
--- /dev/null
+++ b/layout/base/crashtests/1286889.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<rt><span style="background:red;"><li></li>&#x200f;
diff --git a/layout/base/crashtests/128855-1.html b/layout/base/crashtests/128855-1.html
new file mode 100644
index 0000000000..537fdf137e
--- /dev/null
+++ b/layout/base/crashtests/128855-1.html
@@ -0,0 +1,8 @@
+<HTML><HEAD><TITLE>Testcase for bug 128855</TITLE></HEAD>
+<BODY>
+
+<P style="FONT-VARIANT: small-caps">2.3&nbsp;
+éÄÅÎÔÉÆÉËÁÔÏÒÙ.........................................................................................................................................................................
+</P>
+
+</BODY></HTML>
diff --git a/layout/base/crashtests/1288608.html b/layout/base/crashtests/1288608.html
new file mode 100644
index 0000000000..52019a965a
--- /dev/null
+++ b/layout/base/crashtests/1288608.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ document.body.style.overflow = "scroll";
+ c.style.visibility = "";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="c" style="position: relative; transition: 2s; display: table-cell; bottom: 0.1vw;"></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1288946-1.html b/layout/base/crashtests/1288946-1.html
new file mode 100644
index 0000000000..6186b0f84d
--- /dev/null
+++ b/layout/base/crashtests/1288946-1.html
@@ -0,0 +1,9 @@
+<head id=test0>>
+<body id=test2>}Uh<map name=foo id=test3 alt="" href=http://a\b:c\d@foo.com</body><style>
+* { kwidth: 104; letter-spacing: 25710.0134799cm;</style><script>
+test2.style.display = "initial"
+try{test3.insertBefore(test0, test3.childNodes[0 % test3.childNodes.length]); } catch(e) { try{;}catch(e){} }
+test0.parentNode.style.display = "contents"
+window.scrollTo();
+test0.outerHTML = " ~M2Wnj S`awf## Ok 57= x? 7, ,nFQU=L RvKD K5 6 s qb.0cG i~av_: bY5Hr D%~u6:;";
+</script>
diff --git a/layout/base/crashtests/1288946-2a.html b/layout/base/crashtests/1288946-2a.html
new file mode 100644
index 0000000000..7b28da5608
--- /dev/null
+++ b/layout/base/crashtests/1288946-2a.html
@@ -0,0 +1,13 @@
+<body style="width:0"><strong>a b<span id="span"><script>
+var code = document.createElement("code");
+var small = document.createElement("small");
+small.appendChild(document.createTextNode("x"));
+
+document.implementation;
+
+span.insertBefore(code, span.firstChild);
+span.style.display = "contents";
+window.scrollTo();
+code.remove();
+span.insertBefore(small, span.firstChild);
+</script></strong>
diff --git a/layout/base/crashtests/1288946-2b.html b/layout/base/crashtests/1288946-2b.html
new file mode 100644
index 0000000000..30307ba57d
--- /dev/null
+++ b/layout/base/crashtests/1288946-2b.html
@@ -0,0 +1,13 @@
+<body style="width:0"><strong>a b<span id="span"><script>
+var code = document.createElement("code");
+var small = document.createElement("span");
+small.appendChild(document.createTextNode("x"));
+
+document.implementation;
+
+span.insertBefore(code, span.firstChild);
+span.style.display = "contents";
+window.scrollTo();
+code.remove();
+span.insertBefore(small, span.firstChild);
+</script></strong>
diff --git a/layout/base/crashtests/1297835.html b/layout/base/crashtests/1297835.html
new file mode 100644
index 0000000000..47c9e3ea4d
--- /dev/null
+++ b/layout/base/crashtests/1297835.html
@@ -0,0 +1,6 @@
+<body onload="document.documentElement.offsetWidth; document.querySelector('details').style.color = 'green'">
+ <details style="display: block; overflow: scroll;">
+ <summary>Some summary</summary>
+ The details
+ </details>
+</body>
diff --git a/layout/base/crashtests/1299736-1.html b/layout/base/crashtests/1299736-1.html
new file mode 100644
index 0000000000..078a120378
--- /dev/null
+++ b/layout/base/crashtests/1299736-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<title>Testcase, bug 1299736</title>
+
+<div id="A" style="transform: translateX(50px)">
+ <div id="B">
+ <div id="C" style="position: fixed">
+ </div>
+ </div>
+</div>
+
+<script>
+ document.getElementById("C").offsetLeft; // flush
+ document.getElementById("B").style.transform = "translateX(50px)";
+ document.getElementById("A").style.transform = "";
+</script>
diff --git a/layout/base/crashtests/1308793.svg b/layout/base/crashtests/1308793.svg
new file mode 100644
index 0000000000..d2ba481cff
--- /dev/null
+++ b/layout/base/crashtests/1308793.svg
@@ -0,0 +1,31 @@
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<svg xmlns="http://www.w3.org/2000/svg">
+
+<title>Crash test for bug 1308793</title>
+
+<style id="s" type="text/css">
+tspan { fill: green;}
+.flex { display:flex; }
+.grid { display:grid; }
+.col { columns: 3; }
+</style>
+
+<text class="flex">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+<text class="grid">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+<text class="col">
+ <tspan x="100" y="50">A</tspan>
+ B
+</text>
+
+</svg>
diff --git a/layout/base/crashtests/1308848-1.html b/layout/base/crashtests/1308848-1.html
new file mode 100644
index 0000000000..894eb448af
--- /dev/null
+++ b/layout/base/crashtests/1308848-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<table><tbody></tbody><tfoot></tfoot></table>
+<script>
+ document.body.offsetTop;
+ let parent = document.querySelector("table");
+ let comment = document.createComment("hello");
+ let footer = document.querySelector("tfoot");
+ parent.insertBefore(comment, footer);
+</script>
diff --git a/layout/base/crashtests/1308848-2.html b/layout/base/crashtests/1308848-2.html
new file mode 100644
index 0000000000..a83c395de5
--- /dev/null
+++ b/layout/base/crashtests/1308848-2.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<table><tbody></tbody><tfoot></tfoot></table>
+<script>
+ document.body.offsetTop;
+ let parent = document.querySelector("table");
+ let pi = document.createProcessingInstruction('xml-stylesheet', 'href="test.css"');
+ let footer = document.querySelector("tfoot");
+ parent.insertBefore(pi, footer);
+</script>
diff --git a/layout/base/crashtests/133410-1.html b/layout/base/crashtests/133410-1.html
new file mode 100644
index 0000000000..345efbd03d
--- /dev/null
+++ b/layout/base/crashtests/133410-1.html
@@ -0,0 +1,27 @@
+<html>
+ <head>
+ <title>Bug 133410</title>
+ </head>
+
+ <body>
+
+ <table>
+ <tr>
+ <td>
+ <form>
+ <input type="text">
+ <input type="submit" value="Search">
+ <!-- note missing form close tag -->
+ </td>
+ </tr>
+ </table>
+
+ <table>
+ <span>
+ <!-- simple animated gif -->
+ <img src="../../../testing/crashtest/images/animfish.gif">
+ </span>
+ </table>
+
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1338772-1.html b/layout/base/crashtests/1338772-1.html
new file mode 100644
index 0000000000..70bf09dcaa
--- /dev/null
+++ b/layout/base/crashtests/1338772-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function loadHandler() {
+ var outer = document.getElementById("outer");
+ var inner = document.createElement("iframe");
+ inner.height = "1px";
+ outer.contentDocument.body.appendChild(inner);
+
+ // Flush reflow inside our inner iframe. This ensures that our inner.height
+ // tweak further down will actually invoke the resize handler.
+ inner.contentWindow.offsetTop;
+
+ // Set up resize handler (which ends the test, one way or another)
+ inner.contentWindow.onresize = () => {
+ outer.remove();
+ document.documentElement.removeAttribute("class");
+ };
+
+ // Trigger the resize handler:
+ inner.height = "5px";
+ inner.offsetTop;
+}
+
+// This function is a hack to avoid sporadic test-failures with...
+// "...timed out waiting for reftest-wait to be removed".
+// Occasionally, it seems this test loses a race condition of some sort, and
+// its resize handler isn't invoked. When that happens (and specifically, when
+// the test runs for longer than 500ms), we clear reftest-wait and call the
+// run a "pass" (which is kind of valid, because we didn't crash!) and move on.
+function setupFailsafe() {
+ setTimeout(() => {
+ document.documentElement.removeAttribute("class");
+ }, 500);
+}
+</script>
+<body onload="setupFailsafe()">
+ <iframe id="outer"
+ src="data:text/html,<html><body>"
+ onload="loadHandler()">
+ </iframe>
+</body>
diff --git a/layout/base/crashtests/1340571.html b/layout/base/crashtests/1340571.html
new file mode 100644
index 0000000000..f46720cca1
--- /dev/null
+++ b/layout/base/crashtests/1340571.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style id=z></style>
+ <script>
+ o1 = document.createElement('embed');
+ document.getElementById('z').scrollLeft;
+ document.styleSheets[0].insertRule("* { flex-basis: calc(1rem) }", 0);
+ try { window.find("a", true, true, false, false, true, false); } catch(e) {};
+ document.replaceChild(o1, document.documentElement);
+ document.documentElement.appendChild(document.createElement('style'));
+ document.styleSheets[0].insertRule("* { font: small roman inherit }", 0);
+ </script>
+ </head>
+</html>
diff --git a/layout/base/crashtests/1343139-1.html b/layout/base/crashtests/1343139-1.html
new file mode 100644
index 0000000000..c1a90dbc1c
--- /dev/null
+++ b/layout/base/crashtests/1343139-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+ let o0 = document.createElement("span"),
+ o1 = document.createElement("div");
+ document.documentElement.appendChild(o0);
+ o0.appendChild(o1);
+ o0.animate([{"filter": "invert(96%)"}], 100);
+ o1.animate([{"mask": "linear-gradient(red,blue)", "transform": "none"}], 100);
+
+ requestAnimationFrame(() => {
+ document.documentElement.classList.remove("reftest-wait");
+ });
+};
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1343606.html b/layout/base/crashtests/1343606.html
new file mode 100644
index 0000000000..ac10656007
--- /dev/null
+++ b/layout/base/crashtests/1343606.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+body {
+ columns: 5;
+ column-fill: auto;
+ height: 100px;
+}
+div {
+ display: grid;
+ grid-template-columns: 30px 30px 30px;
+ grid-auto-rows: 30px;
+ border:5px solid;
+}
+span {
+ border:1px solid black;
+}
+</style>
+<script>
+setTimeout(function(){ window.close(); },1000);
+window.onload = function(){
+ let a = document.getElementsByTagName("x")[0],
+ b = document.createTextNode("カ쾊紋鴺");
+ a.appendChild(b);
+ setTimeout(function(){
+ b.remove();
+ }, 0);
+};
+</script>
+</head>
+<body>
+<div>
+<span><x>æŸïžŸï¬¬í¤</x></span>
+The quick brown fox jumps over the lazy dog.
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/1343937.html b/layout/base/crashtests/1343937.html
new file mode 100644
index 0000000000..91fb9b2059
--- /dev/null
+++ b/layout/base/crashtests/1343937.html
@@ -0,0 +1,13 @@
+<html>
+ <head>
+ <style>
+ .c3::before {
+ position: fixed;
+ overflow-x: hidden;
+ }
+ </style>
+ </head>
+ <body>
+ <optgroup class='c3'></optgroup>
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1352380.html b/layout/base/crashtests/1352380.html
new file mode 100644
index 0000000000..963b234677
--- /dev/null
+++ b/layout/base/crashtests/1352380.html
@@ -0,0 +1,9 @@
+<div style="
+ height: 20px;
+ background:
+ linear-gradient(green 20%,
+ red 60%,
+ white,
+ 40%, /* midpoint */
+ rgba(0,0,255,0.5),
+ rgba(0,255,255,0.5) 60%)"></div>
diff --git a/layout/base/crashtests/1362423-1.html b/layout/base/crashtests/1362423-1.html
new file mode 100644
index 0000000000..554eef6ccb
--- /dev/null
+++ b/layout/base/crashtests/1362423-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<head>
+ <style>
+ * {
+ unicode-bidi: bidi-override;
+ column-width: 1px;
+ height: 1px;
+ }
+ *::first-letter { unicode-bidi: auto; }
+ </style>
+ <script>
+ o1 = document.createElement('div');
+ o1.innerText = "0\u202A\uD843\uDFF30\u8F4F\DFC\u5DFC9\u0669u";
+ document.documentElement.appendChild(o1);
+ </script>
+</head>
diff --git a/layout/base/crashtests/1381323.html b/layout/base/crashtests/1381323.html
new file mode 100644
index 0000000000..2406afd746
--- /dev/null
+++ b/layout/base/crashtests/1381323.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<table>
+ <tr>
+ <td><div style="display: contents">foo</div></td>
+ </tr>
+</table>
+<script>
+ document.body.offsetTop;
+ document.querySelector('div').style.color = "green";
+</script>
diff --git a/layout/base/crashtests/1382534.html b/layout/base/crashtests/1382534.html
new file mode 100644
index 0000000000..5d7654c17b
--- /dev/null
+++ b/layout/base/crashtests/1382534.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+ .container {
+ position: absolute;
+ top: -1px;
+ bottom: -1px;
+ left: 0;
+ width: 100%;
+ clip: rect(0, auto, auto, 0);
+ clip-path: inset(0 0 0 0)
+ }
+ .picture {
+ position: fixed;
+ }
+ </style>
+ </head>
+ <body>
+ <div class="container">
+ <div class="picture">
+ <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==">
+ </div>
+ </div>
+ </body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1388625-1.html b/layout/base/crashtests/1388625-1.html
new file mode 100644
index 0000000000..3a45c60eb5
--- /dev/null
+++ b/layout/base/crashtests/1388625-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+ div::first-line { color: green }
+</style>
+<body style="width: 100px" onload="document.body.style.width = 'auto'">
+ <div>
+ <span style="display: ruby-base-container">Some</span>
+ <span style="display: ruby-base-container">text that is fairly long</span>
+ </div>
+</body>
diff --git a/layout/base/crashtests/1390389.html b/layout/base/crashtests/1390389.html
new file mode 100644
index 0000000000..6263ce1b69
--- /dev/null
+++ b/layout/base/crashtests/1390389.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+a = document.createElement("span");
+document.documentElement.appendChild(a);
+b = document.createElement("span");
+b.appendChild(document.createElement("div"));
+a.appendChild(b);
+a.appendChild(document.createElement("col"));
+</script>
+</head>
+<body>
+<style>
+q { color: red; }
+</style>
+</body>
+</html>
diff --git a/layout/base/crashtests/1391736.html b/layout/base/crashtests/1391736.html
new file mode 100644
index 0000000000..a4a90e9f9c
--- /dev/null
+++ b/layout/base/crashtests/1391736.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+ a = document.createElement("div")
+ b = document.createElement("div")
+ a.appendChild(b)
+ document.documentElement.appendChild(a)
+ a.style.overflow = "scroll"
+ a.style.columnWidth = "calc(-15px)"
+ b.style.display = "table-caption"
+ setTimeout(() => {
+ a.style.stopColor = "#000"
+ b.style.gridColumn = "crispEdges"
+ window.saved = b.offsetWidth;
+ document.documentElement.className = "";
+ }, 0)
+}
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1395591-1.html b/layout/base/crashtests/1395591-1.html
new file mode 100644
index 0000000000..2fca2ab4d0
--- /dev/null
+++ b/layout/base/crashtests/1395591-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+span {
+ position: fixed;
+}
+div {
+ display: contents
+}
+body::first-line {}
+</style>
+<div><span></span></div>
diff --git a/layout/base/crashtests/1395715-1.html b/layout/base/crashtests/1395715-1.html
new file mode 100644
index 0000000000..00c9291e60
--- /dev/null
+++ b/layout/base/crashtests/1395715-1.html
@@ -0,0 +1,17 @@
+<html class="e1">
+<style>
+.e1:first-letter {}
+#e0:first-line {}
+</style>
+<script>
+document.documentElement.id = "e0"
+a = document.createElement("form")
+document.documentElement.appendChild(a)
+document.documentElement.appendChild(document.createTextNode("}"))
+b = document.createElement('m')
+document.documentElement.appendChild(b)
+c = document.createElement('i')
+b.scrollLeftMax
+c.textContent = "\uDCFF"
+c.lastChild.before(undefined, a)
+</script>
diff --git a/layout/base/crashtests/1397398-1.html b/layout/base/crashtests/1397398-1.html
new file mode 100644
index 0000000000..3a55d3740c
--- /dev/null
+++ b/layout/base/crashtests/1397398-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+a = document.createElement("x")
+document.documentElement.appendChild(a)
+a.animate([{ "filter": "sepia(7)" }], 3000)
+b = document.createElement("caption")
+a.appendChild(b)
+a.style = "display:contents"
+//DDBEGIN
+b.animate([{ "padding": "912q" }], 1500)
+//DDEND
+a.animate([{}])
+}
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1397398-2.html b/layout/base/crashtests/1397398-2.html
new file mode 100644
index 0000000000..2c36b13a82
--- /dev/null
+++ b/layout/base/crashtests/1397398-2.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+window.onload = () => {
+a = document.createElement("x")
+document.documentElement.appendChild(a)
+a.animate([{ "color": "green" }], 3000)
+b = document.createElement("caption")
+a.appendChild(b)
+a.style = "display:contents"
+//DDBEGIN
+b.animate([{ "text-indent": "912q" }], 1500)
+//DDEND
+a.animate([{}])
+}
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1397398-3.html b/layout/base/crashtests/1397398-3.html
new file mode 100644
index 0000000000..7a47354931
--- /dev/null
+++ b/layout/base/crashtests/1397398-3.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<span id="x" style="display: contents">
+ <span style="display: table-caption">
+</span>
+<script>
+ document.body.offsetWidth;
+ x.style.color = "green";
+</script>
diff --git a/layout/base/crashtests/1398500.html b/layout/base/crashtests/1398500.html
new file mode 100644
index 0000000000..51bae713e0
--- /dev/null
+++ b/layout/base/crashtests/1398500.html
@@ -0,0 +1,21 @@
+<!-- Quirks mode on purpose -->
+<html>
+ <head>
+ <style>
+ body { overflow: scroll; border: 1px solid green; }
+ </style>
+ <script>
+ onload = function() {
+ var newBody = document.createElement("body");
+ newBody.textContent = "This element should not have scrollbars!";
+ document.documentElement.appendChild(newBody);
+ window.nooptimize = newBody.offsetWidth;
+ document.body.remove();
+ newBody.scrollHeight; // Asserts in a debug build
+ }
+ </script>
+ </head>
+ <body>
+ First body
+ </body>
+</html>
diff --git a/layout/base/crashtests/1400438-1.html b/layout/base/crashtests/1400438-1.html
new file mode 100644
index 0000000000..366e1104ff
--- /dev/null
+++ b/layout/base/crashtests/1400438-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 100px; height: 100px;
+ border-left: 10px solid;
+ -moz-border-left-colors: red green;
+}
+</style>
+<div></div>
diff --git a/layout/base/crashtests/1400599-1.html b/layout/base/crashtests/1400599-1.html
new file mode 100644
index 0000000000..19c696ca21
--- /dev/null
+++ b/layout/base/crashtests/1400599-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<script>
+onload = function() {
+ document.documentElement.remove();
+}
+</script>
+<body style="overflow: scroll">
diff --git a/layout/base/crashtests/1401739.html b/layout/base/crashtests/1401739.html
new file mode 100644
index 0000000000..ff6db259cd
--- /dev/null
+++ b/layout/base/crashtests/1401739.html
@@ -0,0 +1,11 @@
+<style>
+html { column-width:0 }
+</style>
+<script>
+document.documentElement.appendChild(document.createElement("option"))
+document.documentElement.appendChild(document.createElement("th"))
+document.styleSheets[0].insertRule("c{", 0)
+document.documentElement.getBoundingClientRect()
+document.styleSheets[0].deleteRule(0)
+</script>
+<body></body>
diff --git a/layout/base/crashtests/1401840.html b/layout/base/crashtests/1401840.html
new file mode 100644
index 0000000000..99ac25c1b1
--- /dev/null
+++ b/layout/base/crashtests/1401840.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+ html { display: table; }
+ body { overflow: scroll; }
+</style>
+<script>
+ onload = function() {
+ document.body.style.display = 'inline';
+ }
+</script>
diff --git a/layout/base/crashtests/1402476.html b/layout/base/crashtests/1402476.html
new file mode 100644
index 0000000000..df3ad1e894
--- /dev/null
+++ b/layout/base/crashtests/1402476.html
@@ -0,0 +1,13 @@
+<html class="reftest-wait">
+ <script>
+ a = document.createElement("style")
+ a.appendChild(document.createTextNode("*:first-letter { }"))
+ document.documentElement.appendChild(a)
+ a.style.display = "contents"
+ setTimeout(() => {
+ a.appendChild(document.createElement("x"));
+ a.lastChild.offsetWidth;
+ document.documentElement.className = "";
+ }, 0)
+ </script>
+</html>
diff --git a/layout/base/crashtests/1404789-2.html b/layout/base/crashtests/1404789-2.html
new file mode 100644
index 0000000000..667618141b
--- /dev/null
+++ b/layout/base/crashtests/1404789-2.html
@@ -0,0 +1,2 @@
+<!doctype html>
+<iframe style="display: none" src="1404789-1.html"></iframe>
diff --git a/layout/base/crashtests/1406562.html b/layout/base/crashtests/1406562.html
new file mode 100644
index 0000000000..f5c97b0a16
--- /dev/null
+++ b/layout/base/crashtests/1406562.html
@@ -0,0 +1,15 @@
+<style>
+.class5 { columns: 0px; }
+li::first-letter { color: red; }
+.class5 { list-style-position: inside; }
+</style>
+<script>
+function jsfuzzer() {
+ htmlvar00001.appendChild(htmlvar00027);
+}
+</script>
+<body onload=jsfuzzer()>
+<a id="htmlvar00001">
+<ul class="class5">
+<li>`</li>
+<li id="htmlvar00027">
diff --git a/layout/base/crashtests/1409147.html b/layout/base/crashtests/1409147.html
new file mode 100644
index 0000000000..dcdbe9d907
--- /dev/null
+++ b/layout/base/crashtests/1409147.html
@@ -0,0 +1,20 @@
+<style>
+#htmlvar00008 { background-blend-mode: screen, normal; }
+#htmlvar00009 { mix-blend-mode: darken; }
+.class1 {
+ outline: auto;
+ will-change: z-index;
+ background-image: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7);
+}
+</style>
+<script>
+function jsfuzzer() {
+ try { htmlvar00010.after(htmlvar00005); } catch(e) { }
+}
+</script>
+<body onload=jsfuzzer()>
+<p id="htmlvar00005"></p>
+<font id="htmlvar00008"
+class="class1">
+<button id="htmlvar00009"></button>
+<canvas id="htmlvar00010">
diff --git a/layout/base/crashtests/1411138.html b/layout/base/crashtests/1411138.html
new file mode 100644
index 0000000000..028e8c9222
--- /dev/null
+++ b/layout/base/crashtests/1411138.html
@@ -0,0 +1,13 @@
+<!-- Needs to be in quirks mode -->
+<html>
+ <head>
+ <script>
+ try { o1 = document.createElement('frameset') } catch(e) { }
+ try { o2 = document.createElement('body') } catch(e) { }
+ try { o2.style.overflow = 'auto'; } catch (e) {}
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { o2.scrollLeft } catch(e) { }
+ </script>
+ </head>
+</html>
diff --git a/layout/base/crashtests/1414100.html b/layout/base/crashtests/1414100.html
new file mode 100644
index 0000000000..bb7ec35673
--- /dev/null
+++ b/layout/base/crashtests/1414100.html
@@ -0,0 +1,9 @@
+<script>
+function jsfuzzer() {
+ var a = htmlvar00024.attachShadow({ mode: "open" });
+ a.innerHTML = htmlvar00013.outerHTML;
+}
+</script>
+<body onload=jsfuzzer()>
+<div id="htmlvar00013">
+<q id="htmlvar00024">
diff --git a/layout/base/crashtests/1414303.html b/layout/base/crashtests/1414303.html
new file mode 100644
index 0000000000..0379ca90a7
--- /dev/null
+++ b/layout/base/crashtests/1414303.html
@@ -0,0 +1,11 @@
+<style>
+ * { counter-reset: c; }
+</style>
+<script>
+function go() {
+ host.attachShadow({ mode: "open" }).innerHTML = form.outerHTML;
+}
+</script>
+<body onload=go()>
+<form id="form" style="counter-reset: c">
+ <div id="host">
diff --git a/layout/base/crashtests/1419762.html b/layout/base/crashtests/1419762.html
new file mode 100644
index 0000000000..08a56106db
--- /dev/null
+++ b/layout/base/crashtests/1419762.html
@@ -0,0 +1,15 @@
+<style id='style_1'>
+ :first-child { display: table-column-group; }
+</style>
+<script>
+ try { o1 = document.createElement('isindex') } catch(e) { }
+ try { o2 = document.createElement('input') } catch(e) { }
+ try { o3 = document.createElement('optgroup') } catch(e) { }
+ try { o4 = document.createElement('col') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { o1.appendChild(o2) } catch(e) { }
+ try { o1.appendChild(o3) } catch(e) { }
+ try { document.documentElement.offsetTop; } catch (e) { }
+ try { document.documentElement.appendChild(o4) } catch(e) { }
+ try { document.styleSheets[0].insertRule('optgroup::first-line { list-style-type: japanese-formal; }', 0); } catch(e) { }
+</script>
diff --git a/layout/base/crashtests/1419802.html b/layout/base/crashtests/1419802.html
new file mode 100644
index 0000000000..5c75dd0b57
--- /dev/null
+++ b/layout/base/crashtests/1419802.html
@@ -0,0 +1,9 @@
+<script>
+ try { o1 = document.createElement('i') } catch(e) { }
+ try { o2 = document.createElement('style') } catch(e) { }
+ try { document.documentElement.appendChild(o1) } catch(e) { }
+ try { document.head.appendChild(o2) } catch(e) { }
+ try { document.writeln("<data id='id0'></data>\n<style id='id0'>#id0{margin-left:619}#id0{display:ruby-base}</style>") } catch(e) { }
+ try { o1.innerHTML = "<style>" } catch(e) { }
+ try { document.styleSheets[2].insertRule(":first-letter { }", 0); } catch(e) { }
+</script>
diff --git a/layout/base/crashtests/1420533.html b/layout/base/crashtests/1420533.html
new file mode 100644
index 0000000000..9fa247f686
--- /dev/null
+++ b/layout/base/crashtests/1420533.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<style>
+#a { display: contents; }
+#a::before { content: "" }
+</style>
+<marquee>
+<div id="a"></div>
+</marquee>
+<script>
+document.body.offsetTop;
+a.remove();
+</script>
diff --git a/layout/base/crashtests/1422908.html b/layout/base/crashtests/1422908.html
new file mode 100644
index 0000000000..723617ede2
--- /dev/null
+++ b/layout/base/crashtests/1422908.html
@@ -0,0 +1,11 @@
+<html>
+ <head>
+ <style>
+ * { column-count: 1 }
+ </style>
+ </head>
+</html>
+<body>
+<div style="">
+ <fieldset style="transform-style:preserve-3d">
+ <fieldset style="position:fixed">
diff --git a/layout/base/crashtests/1425893.html b/layout/base/crashtests/1425893.html
new file mode 100644
index 0000000000..913810b4cd
--- /dev/null
+++ b/layout/base/crashtests/1425893.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<a id="link" href=""></a>
+<script>
+document.body.offsetTop;
+link.appendChild(document.createElement("fieldset"));
+</script>
diff --git a/layout/base/crashtests/1425959.html b/layout/base/crashtests/1425959.html
new file mode 100644
index 0000000000..f0cc330f84
--- /dev/null
+++ b/layout/base/crashtests/1425959.html
@@ -0,0 +1,12 @@
+<style>
+* { position: absolute; }
+#a, button::first-line { display: grid; }
+</style>
+<script>
+function go() {
+ a.insertBefore(document.createElement("a"), b);
+}
+</script>
+<body onload=go()>
+<button id="a">
+<span id="b">
diff --git a/layout/base/crashtests/1428353.html b/layout/base/crashtests/1428353.html
new file mode 100644
index 0000000000..acc6849abb
--- /dev/null
+++ b/layout/base/crashtests/1428353.html
@@ -0,0 +1,15 @@
+<script>
+ try { o1 = document.createTextNode(''); } catch(e) { }
+ try { o2 = document.createElement('slot') } catch(e) { }
+ try { o3 = document.createElement('s') } catch(e) { }
+ try { o4 = document.getSelection() } catch(e) { }
+ try { document.head.appendChild(o1) } catch(e) { }
+ try { document.documentElement.appendChild(o2) } catch(e) { }
+ try { document.head.appendChild(o3) } catch(e) { }
+ try { o1.after('', document.head) } catch(e) { }
+ try { document.designMode = 'on' } catch(e) { }
+ try { document.execCommand('formatblock', false, 'pre') } catch(e) { }
+ try { document.designMode = 'off' } catch(e) { }
+ try { o5 = o4.getRangeAt(0) } catch(e) { }
+ try { o5.selectNode(o3); } catch(e) { }
+</script>
diff --git a/layout/base/crashtests/1428892.html b/layout/base/crashtests/1428892.html
new file mode 100644
index 0000000000..3c90997e95
--- /dev/null
+++ b/layout/base/crashtests/1428892.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<script>
+img = new Image(0, 2)
+document.documentElement.appendChild(img)
+frm = document.createElement("iframe")
+frm.align = "right"
+document.documentElement.appendChild(frm)
+img.append(undefined, undefined, "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\rAAAAA=[AAAAAAAAAAAAAA\u066B\bAAAAAAAAAAAAAAAA*<AAAAAA")
+</script>
+<style>
+html { width: 300px; column-width: 100px; height: 100px; }
+</style>
+<div style="float: left; height: 5px;"></div>
+<div style="clear: left;"><feDiffuseLighting><div style="float: right;"></div></div>
diff --git a/layout/base/crashtests/1429088.html b/layout/base/crashtests/1429088.html
new file mode 100644
index 0000000000..03a3816d43
--- /dev/null
+++ b/layout/base/crashtests/1429088.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<div id="host" style="display: none"></div>
+<script>
+ let shadowRoot = host.attachShadow({ mode: "open" });
+ shadowRoot.appendChild(document.createTextNode('foo'));
+ document.body.offsetTop;
+ shadowRoot.insertBefore(document.createElement('bar'), shadowRoot.firstChild);
+</script>
diff --git a/layout/base/crashtests/1429961.html b/layout/base/crashtests/1429961.html
new file mode 100644
index 0000000000..da271d7607
--- /dev/null
+++ b/layout/base/crashtests/1429961.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+:first-of-type::after {
+ content: counter(ctr385,korean-hanja-informal) counter(ctr636);
+}
+:only-of-type {
+ padding-inline-end:83%;
+}
+</style>
+<s>
+<a id='a'></a>
+<script>
+let o = document.getElementById('a');
+document.body.offsetTop;
+o.parentNode.appendChild(o);
+</script>
diff --git a/layout/base/crashtests/1429962.html b/layout/base/crashtests/1429962.html
new file mode 100644
index 0000000000..a128416f86
--- /dev/null
+++ b/layout/base/crashtests/1429962.html
@@ -0,0 +1,12 @@
+<script>
+function go(){
+ let o=document.getElementById('a');
+ let n=document.createElement('li');
+ o.parentNode.replaceChild(n,o);
+}
+document.addEventListener("DOMContentLoaded", go);
+</script>
+<fieldset>
+<canvas id='a'></canvas>
+<footer>
+
diff --git a/layout/base/crashtests/1435015.html b/layout/base/crashtests/1435015.html
new file mode 100644
index 0000000000..329aaca22f
--- /dev/null
+++ b/layout/base/crashtests/1435015.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<style>
+ div { display: contents; }
+</style>
+<math></math>
+<script>
+ let div = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'div');
+ document.querySelector('math').appendChild(div);
+</script>
diff --git a/layout/base/crashtests/1437155.html b/layout/base/crashtests/1437155.html
new file mode 100644
index 0000000000..151fda9d1e
--- /dev/null
+++ b/layout/base/crashtests/1437155.html
@@ -0,0 +1,13 @@
+<script>
+window.onload=function() {
+ document.getElementById('a').hidden='false';
+}
+</script>
+<a></a>
+<section dir='rtl'>
+<kbd id='a' dir='auto'>
+a
+<style>
+:only-child{ display:contents }
+:first-of-type::first-letter{ background-position:unset }
+</style>
diff --git a/layout/base/crashtests/143862-1a-inner.html b/layout/base/crashtests/143862-1a-inner.html
new file mode 100644
index 0000000000..51dc3e12a8
--- /dev/null
+++ b/layout/base/crashtests/143862-1a-inner.html
@@ -0,0 +1,19 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+window.addEventListener("load", o);
+function o()
+{
+ dump("143862-1-inner.html: B*\n");
+ document.documentElement.offsetHeight;
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1a.html b/layout/base/crashtests/143862-1a.html
new file mode 100644
index 0000000000..099e1661c0
--- /dev/null
+++ b/layout/base/crashtests/143862-1a.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1a-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-1b-inner.html b/layout/base/crashtests/143862-1b-inner.html
new file mode 100644
index 0000000000..3a99dbb907
--- /dev/null
+++ b/layout/base/crashtests/143862-1b-inner.html
@@ -0,0 +1,17 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+window.addEventListener("DOMContentLoaded", o);
+function o()
+{
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1b.html b/layout/base/crashtests/143862-1b.html
new file mode 100644
index 0000000000..ec40fb0ade
--- /dev/null
+++ b/layout/base/crashtests/143862-1b.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1b-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-1c-inner.html b/layout/base/crashtests/143862-1c-inner.html
new file mode 100644
index 0000000000..ed59d42e68
--- /dev/null
+++ b/layout/base/crashtests/143862-1c-inner.html
@@ -0,0 +1,17 @@
+<title>Testcase, bug 143862</title>
+<style type="text/css">
+html { overflow: hidden; }
+</style>
+<script>
+dump("143862-1-inner.html: A\n");
+o();
+function o()
+{
+ dump("143862-1-inner.html: B\n");
+ document.open();
+ dump("143862-1-inner.html: C\n");
+ parent.document.documentElement.removeAttribute("class");
+ dump("143862-1-inner.html: D\n");
+ document.close();
+}
+</script>
diff --git a/layout/base/crashtests/143862-1c.html b/layout/base/crashtests/143862-1c.html
new file mode 100644
index 0000000000..8893c0c6d5
--- /dev/null
+++ b/layout/base/crashtests/143862-1c.html
@@ -0,0 +1,7 @@
+<html class="reftest-wait">
+<head>
+</head>
+<body>
+<iframe src="143862-1c-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/143862-2.html b/layout/base/crashtests/143862-2.html
new file mode 100644
index 0000000000..f9fb41c61b
--- /dev/null
+++ b/layout/base/crashtests/143862-2.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<title>Testcase, bug 143862</title>
+<style type="text/css" id="one"> html { overflow: hidden; } </style>
+<style type="text/css" id="two"></style>
+<script type="text/javascript">
+function remove(elt) { elt.remove(); }
+function run() {
+ remove(document.getElementById("one"));
+ remove(document.getElementById("two"));
+
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(run, 100);
+</script>
+</html>
diff --git a/layout/base/crashtests/1439016.html b/layout/base/crashtests/1439016.html
new file mode 100644
index 0000000000..5469f79887
--- /dev/null
+++ b/layout/base/crashtests/1439016.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<div id="host">
+ <div slot="slot1">content</div>
+</div>
+<script>
+// NOTE(emilio): the failure mode for this crashtest is asserting whenever the
+// shell goes away.
+let shadowRoot = document.querySelector('#host').attachShadow({ mode: 'open' });
+shadowRoot.innerHTML = `
+ <div id="slot1-container">
+ <slot id="slot1" name="slot1"></slot>
+ <button id="button1">Click here</button>
+ </div>
+`;
+
+document.body.offsetTop;
+shadowRoot.querySelector('#slot1-container').remove();
+</script>
diff --git a/layout/base/crashtests/1442018-1.html b/layout/base/crashtests/1442018-1.html
new file mode 100644
index 0000000000..3093a67bc2
--- /dev/null
+++ b/layout/base/crashtests/1442018-1.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ SpecialPowers.wrap(window).printPreview()?.close();
+})
+</script>
+
+<style>
+@page {
+ /* Default reftest-paged page size: */
+ size: 5in 3in;
+ margin: 0.5in; /* This leaves 2in of height for content */
+}
+body { margin: 0; }
+
+table {
+ border-collapse: collapse;
+ border: 5px solid black;
+}
+th {
+ height: 0.5in;
+ border-bottom: 5px solid green;
+ }
+td {
+ height: 1in;
+ width: 2in;
+ border-bottom: 5px solid orange;
+}
+.float { float: left; }
+.clear { clear: both; }
+</style>
+
+<div class="float">
+ <table>
+ <thead>
+ <tr><th></th></tr>
+ </thead>
+ <tbody>
+ <tr><td></td></tr>
+ <tr><td></td></tr>
+ </tbody>
+ </table>
+</div>
+<div class="clear"></div>
diff --git a/layout/base/crashtests/1442506.html b/layout/base/crashtests/1442506.html
new file mode 100644
index 0000000000..dfcf50d900
--- /dev/null
+++ b/layout/base/crashtests/1442506.html
@@ -0,0 +1,10 @@
+<script>
+function go() {
+ var b = window.getSelection();
+ var c = document.getSelection();
+ b.setBaseAndExtent(document.getElementById("a"), 0, document.body.firstChild, 1);
+ c.deleteFromDocument();
+}
+</script>
+<body onload=go()>
+<p id="a">
diff --git a/layout/base/crashtests/1443027-1.html b/layout/base/crashtests/1443027-1.html
new file mode 100644
index 0000000000..0f0f67b087
--- /dev/null
+++ b/layout/base/crashtests/1443027-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+ <div id="first" style="position:absolute; width: 200px; height: 200px; background-color:blue; z-index: 1"></div>
+ <div style="position:absolute; left: 400px; width: 200px; height: 200px; background-color:green; z-index: 2"></div>
+ <div id="overlay" style="position:absolute; top: 100px; width: 600px; height: 200px; background-color:orange; z-index: 10"></div>
+</body>
+<script>
+ function doTest2() {
+ document.getElementById("overlay").style.zIndex= 11;
+ document.documentElement.removeAttribute('class');
+ }
+ function doTest() {
+ document.getElementById("first").style.zIndex = 3;
+ setTimeout(doTest2, 1000);
+ }
+
+ window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
diff --git a/layout/base/crashtests/1448841-1.html b/layout/base/crashtests/1448841-1.html
new file mode 100644
index 0000000000..39669eb8e7
--- /dev/null
+++ b/layout/base/crashtests/1448841-1.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+<body>
+ <div style="width:500px; height:500px; overflow:scroll; border:1px black solid;">
+ <div id="change" style="width:100px; height:100px; background-color:green; position:relative; top:50px; left:50px"></div>
+ <div style="width:100px; height:100px; background-color:red; z-index:2; position:relative; left:200px"></div>
+ <div style="height:1000px"></div>
+ </div>
+ <div style="width:100px; height:100px; background-color:blue; z-index:1; position:relative; top:-400px"></div>
+</body>
+<script>
+ function doTest() {
+ document.getElementById("change").style.backgroundColor = "orange";
+ document.documentElement.removeAttribute("class");
+ }
+ document.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</html>
diff --git a/layout/base/crashtests/1452839.html b/layout/base/crashtests/1452839.html
new file mode 100644
index 0000000000..d427c26027
--- /dev/null
+++ b/layout/base/crashtests/1452839.html
@@ -0,0 +1,8 @@
+<html>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ document.appendChild(document.createComment(''));
+ document.documentElement.remove();
+})
+</script>
+</html>
diff --git a/layout/base/crashtests/1453196.html b/layout/base/crashtests/1453196.html
new file mode 100644
index 0000000000..387aebe6c1
--- /dev/null
+++ b/layout/base/crashtests/1453196.html
@@ -0,0 +1,15 @@
+<html>
+ <head>
+ <script>
+ function start () {
+ try { o1 = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mtable') } catch (e) {}
+ try { o2 = document.createTextNode("\u202D") } catch (e) {}
+ try { document.documentElement.appendChild(o2) } catch (e) {}
+ try { o1.insertBefore(document.documentElement, o1.childNodes[0]) } catch (e) {}
+ try { document.appendChild(o1) } catch (e) {}
+ try { document.append(o2, undefined) } catch (e) {}
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+ </head>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1453342.html b/layout/base/crashtests/1453342.html
new file mode 100644
index 0000000000..96d3e06118
--- /dev/null
+++ b/layout/base/crashtests/1453342.html
@@ -0,0 +1,30 @@
+<html class="reftest-wait">
+<style id='style'>
+ :first-of-type {
+ grid-column-gap: initial;
+ outline-width: 68.8101389898pc;
+ text-decoration: inherit;
+ font-variant-ligatures: inherit;
+ display: contents;
+ mix-blend-mode: color;
+ will-change: hyphens;
+ }
+</style>
+<script>
+ function frameLoader() {
+ this.contentDocument.writeln("<svg><set attributeName='w'>");
+ document.getElementById('style').type = document.getElementById('style').type;
+ document.documentElement.classList.remove("reftest-wait");
+ }
+
+ function start() {
+ // DDBEGIN
+ o1 = document.createElementNS('http://www.w3.org/1998/Math/MathML', 'mroot');
+ o2 = document.createElement('frame');
+ document.documentElement.appendChild(o1);
+ document.documentElement.appendChild(o2);
+ o2.addEventListener('load', frameLoader);
+ }
+ window.addEventListener('load', start)
+</script>
+</html>
diff --git a/layout/base/crashtests/1453702.html b/layout/base/crashtests/1453702.html
new file mode 100644
index 0000000000..ffccf6b7c7
--- /dev/null
+++ b/layout/base/crashtests/1453702.html
@@ -0,0 +1,16 @@
+<style></style>
+<script id='script'>
+ function start() {
+ try { o1 = document.createElement('p') } catch (e) {}
+ try { o2 = document.createElement('l') } catch (e) {}
+ try { o3 = document.createElement('c') } catch (e) {}
+ try { o4 = document.createElement('textarea') } catch (e) {}
+ try { document.documentElement.appendChild(o4) } catch (e) {}
+ try { o4.scrollLeft = 8 } catch (e) {}
+ try { o4.appendChild(document.getElementById('script')) } catch (e) {}
+ try { document.styleSheets[0].insertRule("* { display:contents !important }", 0) } catch (e) {}
+ try { o3.convertPointFromNode({ }, o1, { }) } catch (e) {}
+ try { document.getElementById('script').appendChild(o2) } catch (e) {}
+ }
+ document.addEventListener('DOMContentLoaded', start)
+</script>
diff --git a/layout/base/crashtests/1458121.html b/layout/base/crashtests/1458121.html
new file mode 100644
index 0000000000..8e226e38cb
--- /dev/null
+++ b/layout/base/crashtests/1458121.html
@@ -0,0 +1,23 @@
+<script>
+window.requestIdleCallback(function(){
+ document.documentElement.style.display="none";
+ document.documentElement.getBoundingClientRect();
+ document.documentElement.style.display="";
+});
+</script>
+<style>
+body {
+ -webkit-border-radius: 16px;
+ overflow-x: hidden;
+ -webkit-filter: blur(0px);
+ width: 1em;
+}
+:root {
+ scroll-snap-destination: left top 36%;
+}
+#a {
+ overflow: scroll;
+ clip-path: url(#x);
+}
+</style>
+<dl id="a">|<dialog open>
diff --git a/layout/base/crashtests/1461749.html b/layout/base/crashtests/1461749.html
new file mode 100644
index 0000000000..e29ffa6774
--- /dev/null
+++ b/layout/base/crashtests/1461749.html
@@ -0,0 +1,19 @@
+<style>
+:last-of-type {
+ display: contents;
+}
+</style>
+<script>
+function start() {
+ o1 = document.createElement('footer')
+ o2 = document.createElement('t')
+ document.documentElement.appendChild(o1)
+ document.documentElement.appendChild(o2)
+ o3 = o1.attachShadow({
+ mode: "open"
+ })
+ o2.getClientRects()
+ o3.innerHTML = ">"
+}
+window.addEventListener('load', start)
+</script>
diff --git a/layout/base/crashtests/1461812.html b/layout/base/crashtests/1461812.html
new file mode 100644
index 0000000000..2ff21e8019
--- /dev/null
+++ b/layout/base/crashtests/1461812.html
@@ -0,0 +1,19 @@
+<style>
+:not(feConvolveMatrix) {
+ width: max-content;
+ column-width: 0em;
+ text-indent: 1pt;
+}
+.cl {
+ padding-bottom: 93vw;
+}
+</style>
+<script>
+function go() {
+ b.appendChild(a);
+}
+</script>
+<marquee id="b">4H</marquee>
+<details class="cl">
+<summary id="a" style="mix-blend-mode:color-dodge">A</summary>
+<style onload="go()">
diff --git a/layout/base/crashtests/1462412.html b/layout/base/crashtests/1462412.html
new file mode 100644
index 0000000000..9e23792235
--- /dev/null
+++ b/layout/base/crashtests/1462412.html
@@ -0,0 +1,9 @@
+<style>
+* {
+-webkit-perspective: 1px;
+will-change: transform;
+</style>
+<span>
+<p>*</p>
+<keygen>
+<!-- a --> \ No newline at end of file
diff --git a/layout/base/crashtests/1463940.html b/layout/base/crashtests/1463940.html
new file mode 100644
index 0000000000..5778d81841
--- /dev/null
+++ b/layout/base/crashtests/1463940.html
@@ -0,0 +1,24 @@
+<style>
+#c {
+clip-path: url(undefined);
+}
+.cl {
+opacity: 0.29556127;
+}
+</style>
+<script>
+function eh1() {
+ a.createCaption();
+ b.vAlign = "top";
+}
+function eh2() {
+ c.deleteCell(0);
+ setTimeout(eh1, 0);
+}
+</script>
+<body onload="eh2()">
+<table id="a">
+<tr id="c">
+<th>
+<th id="b">
+<colgroup class="cl">
diff --git a/layout/base/crashtests/1464641.html b/layout/base/crashtests/1464641.html
new file mode 100644
index 0000000000..bf63e6de82
--- /dev/null
+++ b/layout/base/crashtests/1464641.html
@@ -0,0 +1,25 @@
+<style>
+#a {
+ white-space: pre-wrap;
+ padding-right: 10vh;
+ position: sticky;
+ box-decoration-break: clone;
+}
+* { height: 0rem }
+</style>
+<script>
+function go() {
+ document.createElement("i").append(b)
+}
+</script>
+<body onload=go()>
+<time id="a">
+<audio>
+<video poster="G">
+</audio>
+<shadow id="b">A</tt>
+</shadow>
+<dialog open="">
+</dialog>
+</ins>
+<details>
diff --git a/layout/base/crashtests/1464737.html b/layout/base/crashtests/1464737.html
new file mode 100644
index 0000000000..4c978275a5
--- /dev/null
+++ b/layout/base/crashtests/1464737.html
@@ -0,0 +1,7 @@
+<style>
+.cl { -webkit-transform-style: preserve-3d }
+:not(mask) { -webkit-perspective: 0px }
+:root { columns: 0px }
+</style>
+<textarea class="cl"></textarea>
+<p class="cl">z
diff --git a/layout/base/crashtests/1466638.html b/layout/base/crashtests/1466638.html
new file mode 100644
index 0000000000..fed3e41956
--- /dev/null
+++ b/layout/base/crashtests/1466638.html
@@ -0,0 +1,13 @@
+<style>
+#a {
+ -webkit-transition: all 44s linear;
+ filter: contrast(0.052452386);
+}
+</style>
+<script>
+function go() {
+ a.style.setProperty("background", "url(data:i;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAA) no-repeat scroll right")
+}
+</script>
+<body onload=go()>
+<label id="a">#y
diff --git a/layout/base/crashtests/1467519.html b/layout/base/crashtests/1467519.html
new file mode 100644
index 0000000000..a05741a97f
--- /dev/null
+++ b/layout/base/crashtests/1467519.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<style>
+* {
+ position: fixed;
+ left: 93%;
+ padding-left: 919884193vw;
+ writing-mode: vertical-rl;
+ offset-path: ray(sides 1.8357824493920234rad);
+}
+</style>
diff --git a/layout/base/crashtests/1467688.html b/layout/base/crashtests/1467688.html
new file mode 100644
index 0000000000..b2e73886f2
--- /dev/null
+++ b/layout/base/crashtests/1467688.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8">
+</head>
+<body style="transform:translate(4px)">
+<div id="invalid" style="width:100px; height:100px; background-color:red;"></div>
+<script>
+ function doTest() {
+ document.documentElement.style.perspective = '100px';
+ document.getElementById("invalid").style.backgroundColor = "green";
+ document.documentElement.removeAttribute("class");
+ }
+ window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/1467964.html b/layout/base/crashtests/1467964.html
new file mode 100644
index 0000000000..8de615de12
--- /dev/null
+++ b/layout/base/crashtests/1467964.html
@@ -0,0 +1,2 @@
+<select dir='auto'>
+<option dir='auto'>&#x0866;
diff --git a/layout/base/crashtests/1469354.html b/layout/base/crashtests/1469354.html
new file mode 100644
index 0000000000..ee8ad7d426
--- /dev/null
+++ b/layout/base/crashtests/1469354.html
@@ -0,0 +1,16 @@
+<style>
+:root { columns: 0px }
+hr::first-line {}
+li { display: contents }
+</style>
+<script>
+function go() {
+ a.appendChild(c);
+ c.appendChild(b);
+ b.style.setProperty("float", "left");
+}
+</script>
+<svg onload="go()">
+<hr id="a">
+<dd id="b">
+<li id="c">%
diff --git a/layout/base/crashtests/1470499.html b/layout/base/crashtests/1470499.html
new file mode 100644
index 0000000000..043ac23baf
--- /dev/null
+++ b/layout/base/crashtests/1470499.html
@@ -0,0 +1,22 @@
+<script>
+function go() {
+ a.reset();
+}
+function eh1() {
+ var d = f.gradientTransform.baseVal.consolidate();
+ b.addEventListener("DOMSubtreeModified", eh1);
+ d.setRotate(0,0,6);
+}
+function eh2() {
+ var c = document.createElement("t");
+ b.insertAdjacentElement("beforeEnd",f);
+ var g = new MutationObserver(eh1);
+ g.observe(c, { attributes: true });
+ c.setAttribute("m", "");
+}
+</script>
+<body onload=go()>
+<form id="a" onreset="eh2()">
+<svg>
+<animateMotion id="b" />
+<radialGradient id="f" gradientTransform="matrix(0 2 0 0 0 0)">
diff --git a/layout/base/crashtests/1472020.html b/layout/base/crashtests/1472020.html
new file mode 100644
index 0000000000..ab9192c87b
--- /dev/null
+++ b/layout/base/crashtests/1472020.html
@@ -0,0 +1,11 @@
+<style>
+body { display:contents }
+</style>
+<object id="a"></object>
+<script>
+ document.body.offsetHeight;
+ document.getElementById("a").style.cssText="display:table-column-group"
+ document.body.offsetHeight;
+ document.body.style.cssText="scroll-snap-destination:6%"
+ document.body.offsetHeight;
+</script>
diff --git a/layout/base/crashtests/1472027.html b/layout/base/crashtests/1472027.html
new file mode 100644
index 0000000000..8e39be3564
--- /dev/null
+++ b/layout/base/crashtests/1472027.html
@@ -0,0 +1,7 @@
+<!doctype html>
+<svg>
+<use xlink:href="#a">
+<ellipse id="a">
+<foreignObject>
+<form>
+<textarea required="">A</textarea>
diff --git a/layout/base/crashtests/147320-1.html b/layout/base/crashtests/147320-1.html
new file mode 100644
index 0000000000..f77d523830
--- /dev/null
+++ b/layout/base/crashtests/147320-1.html
@@ -0,0 +1,7 @@
+<html>
+<body>
+ <fieldset style="position: absolute">
+ <legend>text</legend>
+ </fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/1477847.html b/layout/base/crashtests/1477847.html
new file mode 100644
index 0000000000..ac76f745c8
--- /dev/null
+++ b/layout/base/crashtests/1477847.html
@@ -0,0 +1,17 @@
+<style>
+#a {
+ -webkit-filter: invert(0);
+ border-bottom-right-radius: 10px 0px;
+ overflow: scroll;
+ height: 50px;
+}
+.b {
+ perspective: 4px;
+ -webkit-mask: url(data:image/gif;base64,R0lGODlhEAAQAMQAAORHHOVSKudfOulrSOp3WOyDZu6QdvCchPGolfO0o/XBs/fNwfjZ0frl3/zy7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAkAABAALAAAAAAQABAAAAVVICSOZGlCQAosJ6mu7fiyZeKqNKToQGDsM8hBADgUXoGAiqhSvp5QAnQKGIgUhwFUYLCVDFCrKUE1lBavAViFIDlTImbKC5Gm2hB0SlBCBMQiB0UjIQA7);
+ height: 100px;
+}
+.c { -webkit-transform: scale(-1, 1) }
+</style>
+<dl id="a">
+<dt class="b">
+<dialog open="" class="c">
diff --git a/layout/base/crashtests/148245-1.html b/layout/base/crashtests/148245-1.html
new file mode 100644
index 0000000000..749dc5db01
--- /dev/null
+++ b/layout/base/crashtests/148245-1.html
@@ -0,0 +1,11 @@
+<html>
+<head>
+<style type="text/css">
+p:first-letter { float: left; }
+p:first-line { color: black; }
+</style>
+</head>
+<body>
+<p>Ly</p>
+</body>
+</html>
diff --git a/layout/base/crashtests/1486521.html b/layout/base/crashtests/1486521.html
new file mode 100644
index 0000000000..94613acb3d
--- /dev/null
+++ b/layout/base/crashtests/1486521.html
@@ -0,0 +1,11 @@
+<script>
+window.onload=function() {
+ a.options[10] = b;
+ document.execCommand("selectAll", false);
+ document.activeElement.hidden = true;
+ a.options.selectedIndex = 1;
+}
+</script>
+<input contenteditable>
+<select id="a" multiple>
+<option id="b" selected>
diff --git a/layout/base/crashtests/1489149.html b/layout/base/crashtests/1489149.html
new file mode 100644
index 0000000000..e8f6bd1748
--- /dev/null
+++ b/layout/base/crashtests/1489149.html
@@ -0,0 +1,52 @@
+<html class="reftest-wait">
+<head>
+ <style>
+ *|HTML {
+ column-width: calc(15px)
+ }
+
+ *|HTML .class_1 {
+ border-style: dotted;
+ float: inline-end ! important
+ }
+
+ * {
+ block-size: calc(2px);
+ }
+ </style>
+ <script noFuzz>
+ function frameLoad() {
+ setInterval(function() {
+ document.documentElement.appendChild(o1)
+ document.documentElement.appendChild(o2)
+ }, 250)
+ try { document.documentElement.appendChild(o4) } catch (e) {}
+ try { xhr = new XMLHttpRequest() } catch (e) {}
+ try { xhr.open('GET', 'data:text/html,1', false) } catch (e) {}
+ try { xhr.send() } catch (e) {}
+ try { document.documentElement.appendChild(o3) } catch (e) {}
+ try { this.contentWindow.location.reload() } catch (e) {}
+ }
+
+ function start() {
+ o1 = document.createElement('del')
+ o2 = document.createElement('track')
+ o3 = document.createElement('video')
+ o4 = document.createElement('video')
+ o1.setAttribute('class', 'class_1')
+ o2.setAttribute('class', 'class_1')
+ o3.setAttribute('class', 'class_1')
+ frame = document.createElement('iframe')
+ frame.addEventListener('load', frameLoad)
+ setTimeout(function(){
+ document.documentElement.innerHTML="1";
+ document.documentElement.removeAttribute("class");
+ }, 1000);
+ document.firstElementChild.appendChild(frame)
+ }
+
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+ <body></body>
+</head>
+</html>
diff --git a/layout/base/crashtests/1490037.html b/layout/base/crashtests/1490037.html
new file mode 100644
index 0000000000..7d5c6b1040
--- /dev/null
+++ b/layout/base/crashtests/1490037.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<style>
+div {
+ display: contents;
+}
+div::before {
+ content: "";
+ float: left;
+ overflow: scroll;
+}
+</style>
+<div></div>
diff --git a/layout/base/crashtests/149014-1.html b/layout/base/crashtests/149014-1.html
new file mode 100644
index 0000000000..e11f3b79fe
--- /dev/null
+++ b/layout/base/crashtests/149014-1.html
@@ -0,0 +1,44 @@
+<html>
+<body>
+
+<center><h2><h2></center>
+<center><h2>1<h2></center>
+<center><h2>2<h2><center>
+<center><h2>3<ul><h2><center>
+<center><h2>4<h2><center>
+<center><h2>5<h2><center>
+<center><h2>6<h2><center>
+<center><h2>7<h2><center>
+<center><h2>8<h2><center>
+<center><h2>9<h2><center>
+<center><h2>10<h2><center>
+<center><h2>11<ul><h2><center>
+<center><h2>12<h2><center>
+<center><h2>13<h2><center>
+<center><h2>14<h2><center>
+<center><h2>15<h2><center>
+<center><h2>16<h2><center>
+<center><h2>17<h2><center>
+<center><h2>18<h2><center>
+<center><h2>19<h2><center>
+<center><h2>20<h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2><li>Test</li><h2><center>
+<center><h2><li>Test<font color=blue>( CD )</font></li><h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h2>Test<center>
+<center><h2><h2><center>
+<center><h1></h1></center>
+<center><h3>.<h3><center>
+<center><h3><h3><center>
+<center><h2>Test<center>
+<center><h2><h2><center>
+<center><h2><h2><center>
+<center><h1><center>
+
+<input type="text" name="maxbid" size="12" maxlength="12">
+</body>
+</html>
diff --git a/layout/base/crashtests/1494030.html b/layout/base/crashtests/1494030.html
new file mode 100644
index 0000000000..5672c68e65
--- /dev/null
+++ b/layout/base/crashtests/1494030.html
@@ -0,0 +1,22 @@
+<html>
+
+<head>
+ <style>
+ head:nth-last-child(2) {
+ display: contents;
+ }
+
+ *,
+ dd {
+ display: table-caption;
+ </style>
+ <script>
+ function start() {
+ document.title = String.fromCharCode(51, 61, 82, 104, 64, 86, 117, 88, 57, 77, 40, 32, 81, 33, 120, 99, 126, 53, 121, 101);
+ }
+ </script>
+</head>
+
+<body onload="start()"></body>
+
+</html>
diff --git a/layout/base/crashtests/1494332.html b/layout/base/crashtests/1494332.html
new file mode 100644
index 0000000000..a4bf5ff415
--- /dev/null
+++ b/layout/base/crashtests/1494332.html
@@ -0,0 +1,6 @@
+ <style>
+ body::first-letter,
+ body::first-line {
+ </style>
+ XXX {
+ <img id="h" srcset="B"></img>
diff --git a/layout/base/crashtests/150431-1.html b/layout/base/crashtests/150431-1.html
new file mode 100644
index 0000000000..9036cfe362
--- /dev/null
+++ b/layout/base/crashtests/150431-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+<title>bug 150431</title>
+</head>
+<p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1><p><font size=1>
+</body>
+</html>
diff --git a/layout/base/crashtests/1505420.html b/layout/base/crashtests/1505420.html
new file mode 100644
index 0000000000..c02e8f127f
--- /dev/null
+++ b/layout/base/crashtests/1505420.html
@@ -0,0 +1,19 @@
+<style>
+ *::first-letter {}
+</style>
+<li id="list">
+</li>
+<script>
+ list.offsetTop;
+ let s = document.createElement('style');
+ s.textContent = `
+ * {
+ columns: 56;
+ }
+ *::first-line { }
+ `;
+ document.documentElement.appendChild(s);
+ list.innerText = 'ó';
+ list.offsetTop;
+ list.textContent = '';
+</script>
diff --git a/layout/base/crashtests/1506163.html b/layout/base/crashtests/1506163.html
new file mode 100644
index 0000000000..d5fb54388f
--- /dev/null
+++ b/layout/base/crashtests/1506163.html
@@ -0,0 +1,7 @@
+<li style="column-count: 1">
+<summary style="overflow-x: scroll">
+<div style="column-span: all">
+<canvas>
+<meta
+charset="UTF-8"
+<!-- a -->
diff --git a/layout/base/crashtests/1506204.html b/layout/base/crashtests/1506204.html
new file mode 100644
index 0000000000..6c97282b50
--- /dev/null
+++ b/layout/base/crashtests/1506204.html
@@ -0,0 +1,17 @@
+<head id="a">
+<style>
+* {
+ column-count: 1
+}
+.x {
+ position: fixed;
+ column-span: all;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<ol id="b" class="x">A</ol>
diff --git a/layout/base/crashtests/1506314.html b/layout/base/crashtests/1506314.html
new file mode 100644
index 0000000000..c2636a93cc
--- /dev/null
+++ b/layout/base/crashtests/1506314.html
@@ -0,0 +1,15 @@
+<style>
+.x {
+ column-span: all;
+ columns: 0;
+}
+</style>
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<summary id="a" class="x">
+<h5 class="x">
+<a id="b">
diff --git a/layout/base/crashtests/1507244.html b/layout/base/crashtests/1507244.html
new file mode 100644
index 0000000000..4b7a307a85
--- /dev/null
+++ b/layout/base/crashtests/1507244.html
@@ -0,0 +1,25 @@
+<html>
+
+<head>
+ <style>
+ div {
+ column-span: all
+ }
+
+ li {
+ column-count: 53
+ }
+ </style>
+</head>
+
+<body>
+ <li>
+ <table>
+ <caption>
+ <div></div>
+ </caption>
+ </table>
+ </li>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/1510080.html b/layout/base/crashtests/1510080.html
new file mode 100644
index 0000000000..81449d4962
--- /dev/null
+++ b/layout/base/crashtests/1510080.html
@@ -0,0 +1,13 @@
+<script>
+function go() {
+ a.appendChild(b);
+}
+</script>
+<body onload=go()>
+<fieldset id="a" style="height: 1vw; columns: 2px">
+<legend></legend>
+<legend>
+<summary id="b">
+<d>
+A
+<div>
diff --git a/layout/base/crashtests/1510485.html b/layout/base/crashtests/1510485.html
new file mode 100644
index 0000000000..f194d381d4
--- /dev/null
+++ b/layout/base/crashtests/1510485.html
@@ -0,0 +1,7 @@
+<script>
+onload = function(){
+ window.find('e')
+}
+</script>
+<textarea>
+ <!-- E -->
diff --git a/layout/base/crashtests/1511442.html b/layout/base/crashtests/1511442.html
new file mode 100644
index 0000000000..22598c404f
--- /dev/null
+++ b/layout/base/crashtests/1511442.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<style>
+* { scale: 81 1 }
+</style>
+<script>
+function go() {
+ window.getSelection().selectAllChildren(a);
+}
+function eh() {
+ c.beginElementAt(0.67302);
+ b.scrollIntoView(false);
+ document.documentElement.style.setProperty("rotate", "1deg 45 1 -1");
+ document.documentElement.classList.remove("reftest-wait");
+}
+</script>
+<body onload=go()>
+<button id="a">
+<svg id="b">
+<set id="c" onbegin="eh()">
+</html>
diff --git a/layout/base/crashtests/1511535.html b/layout/base/crashtests/1511535.html
new file mode 100644
index 0000000000..d159574b81
--- /dev/null
+++ b/layout/base/crashtests/1511535.html
@@ -0,0 +1,9 @@
+<script>
+function go() {
+ a.style.setProperty("column-span", "all");
+}
+</script>
+<body onload=go()>
+<ol style="columns: 0px">
+<li style="position: fixed">
+<iframe id="a">
diff --git a/layout/base/crashtests/1511563.html b/layout/base/crashtests/1511563.html
new file mode 100644
index 0000000000..6baac7d8fb
--- /dev/null
+++ b/layout/base/crashtests/1511563.html
@@ -0,0 +1,8 @@
+<script>
+document.addEventListener("DOMContentLoaded", function(){
+ document.designMode='on'
+ window.getSelection().modify('move', 'right', 'line')
+})
+</script>
+<br>
+<keygen>
diff --git a/layout/base/crashtests/1516286-empty-mask.html b/layout/base/crashtests/1516286-empty-mask.html
new file mode 100644
index 0000000000..f5ab2b42f4
--- /dev/null
+++ b/layout/base/crashtests/1516286-empty-mask.html
@@ -0,0 +1,14 @@
+<style>
+* {
+ -webkit-transform: scaleY(96);
+ float: left;
+ border-top: 4em solid
+}
+#a {
+ clip-path: polygon(0px 0px, 1px 0px, 48px 1px);
+}
+</style>
+<time>
+<ul>
+<li id="a">
+A
diff --git a/layout/base/crashtests/1524382.html b/layout/base/crashtests/1524382.html
new file mode 100644
index 0000000000..cd0ff67b4c
--- /dev/null
+++ b/layout/base/crashtests/1524382.html
@@ -0,0 +1,12 @@
+<style>
+:root {
+ column-width: 0px;
+}
+</style>
+
+<ul>
+<li style="column-span: all"></li>
+</ul>
+<div style="float:left"></div>
+<div style="position:absolute"></div>
+<div style="position:fixed"></div>
diff --git a/layout/base/crashtests/1524411.html b/layout/base/crashtests/1524411.html
new file mode 100644
index 0000000000..dbe59bc80c
--- /dev/null
+++ b/layout/base/crashtests/1524411.html
@@ -0,0 +1,15 @@
+<style>
+:not(text) {
+ break-inside: avoid;
+}
+* {
+ columns: 0px;
+ padding-top: 87px;
+}
+</style>
+<ol style="font: 1em/1 Ahem, sans-serif">
+A
+A
+<li style="rotate:1deg; float:left">
+<hr>
+A
diff --git a/layout/base/crashtests/1533885.html b/layout/base/crashtests/1533885.html
new file mode 100644
index 0000000000..316e904729
--- /dev/null
+++ b/layout/base/crashtests/1533885.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+ <style>
+ text, :root {
+ column-width: 0;
+ }
+ </style>
+ <script>
+ function start () {
+ document.getElementById('template').style.setProperty('scroll-behavior', 'smooth')
+ }
+ </script>
+</head>
+<body onload="start()">
+<sup>
+ <h4>b&quot;4C@26Rc8:nbxI</h4>
+ <template id="template">
+ </template>
+</sup>
+</body>
+</html>
diff --git a/layout/base/crashtests/1534146.html b/layout/base/crashtests/1534146.html
new file mode 100644
index 0000000000..c02c978c3b
--- /dev/null
+++ b/layout/base/crashtests/1534146.html
@@ -0,0 +1,15 @@
+<style>
+* {
+ column-width: 1em;
+ column-span: all;
+}
+</style>
+<script>
+function go() {
+ a.setAttribute("contenteditable", "true")
+}
+</script>
+<body onload=go()>
+<option id="a">
+<time>
+<input style="display: -webkit-flex" type="reset">
diff --git a/layout/base/crashtests/1535945.html b/layout/base/crashtests/1535945.html
new file mode 100644
index 0000000000..3bea448c7f
--- /dev/null
+++ b/layout/base/crashtests/1535945.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+<style>
+.c {
+ scroll-behavior: smooth;
+ border-style: groove;
+}
+* {
+ margin-right: 52vh;
+ width: 1vh
+}
+:not(feTile) {
+ overflow-x: scroll;
+}
+</style>
+<script>
+function go() {
+ a.scrollTo({left: 1, top: 80})
+ document.documentElement.removeAttribute("class");
+}
+</script>
+<body onload=go()>
+<table background="A">
+A
+<dl>
+<dd id="a" contenteditable="true" class="c">
+</html>
diff --git a/layout/base/crashtests/1539017.html b/layout/base/crashtests/1539017.html
new file mode 100644
index 0000000000..2ab3017acc
--- /dev/null
+++ b/layout/base/crashtests/1539017.html
@@ -0,0 +1,15 @@
+<style>
+* {
+ column-width: 0em;
+ filter: drop-shadow(1px 0px 0px blue);
+}
+</style>
+<script>
+function go() {
+ a.style.cssFloat = "right"
+ document.bgColor = "-"
+}
+</script>
+<body onload=go()>
+<ol id="a">
+<li>
diff --git a/layout/base/crashtests/1539303-iframe.html b/layout/base/crashtests/1539303-iframe.html
new file mode 100644
index 0000000000..3353d91437
--- /dev/null
+++ b/layout/base/crashtests/1539303-iframe.html
@@ -0,0 +1,34 @@
+<html>
+<head>
+ <style>
+ LI {
+ min-width: max-content;
+ column-count: 72;
+ }
+ </style>
+ <script>
+ function start () {
+ document.getElementById('ul').animate({
+ 'scrollSnapPointsY': ['repeat(2vh', ''],
+ 'paddingInlineStart': ['25in', '']
+ }, {
+ delay: 2,
+ fill: 'both',
+ })
+ document.getElementById('ul').scrollIntoView(false)
+ }
+ // reload once
+ if (location.hash == "") {
+ location.hash = "#hello";
+ setTimeout('location.reload()', 0)
+ }
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+
+</head>
+<body>
+<ul id="ul">
+ <li></li>
+</ul>
+</body>
+</html>
diff --git a/layout/base/crashtests/1539303.html b/layout/base/crashtests/1539303.html
new file mode 100644
index 0000000000..3220a12f43
--- /dev/null
+++ b/layout/base/crashtests/1539303.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script>
+function run() {
+ let iframe = document.querySelector("iframe");
+ iframe.contentDocument.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+}
+</script>
+<body>
+ <iframe src="1539303-iframe.html" onload="run()">
+ </iframe>
+</body>
diff --git a/layout/base/crashtests/1541679.html b/layout/base/crashtests/1541679.html
new file mode 100644
index 0000000000..269fe9370e
--- /dev/null
+++ b/layout/base/crashtests/1541679.html
@@ -0,0 +1,20 @@
+<style>
+* {
+ columns: 0px;
+ column-span: all;
+ overflow-x: auto
+}
+</style>
+<script>
+function go() {
+ a.appendChild(c)
+}
+</script>
+<body onload=go()>
+ <menu></menu>
+ <menuitem id="a">
+ <table id="b">
+ <thead>
+ f|_Nz;[fnx?(
+ <tr id="c">
+
diff --git a/layout/base/crashtests/1547261.html b/layout/base/crashtests/1547261.html
new file mode 100644
index 0000000000..47af39161b
--- /dev/null
+++ b/layout/base/crashtests/1547261.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<img src="data:image/svg+xml,<svg width='90px' xmlns='http://www.w3.org/2000/svg'></svg>" style="position: absolute;">
+<img src="data:image/svg+xml,<svg height='90px' xmlns='http://www.w3.org/2000/svg'></svg>" style="position: absolute;">
diff --git a/layout/base/crashtests/1547391.html b/layout/base/crashtests/1547391.html
new file mode 100644
index 0000000000..9339100f4d
--- /dev/null
+++ b/layout/base/crashtests/1547391.html
@@ -0,0 +1,15 @@
+<script>
+function go() {
+ document.body.offsetHeight;
+ b.insertAdjacentHTML("afterBegin", a.innerHTML)
+}
+</script>
+<body onload=go()>
+ <details id="b" open="true" style="column-count: 1">
+ A
+ <div id="a" style="column-span: all">
+ <!-- this comment is essential to trigger the assertion. -->
+ </div>
+ B
+ </details>
+</body>
diff --git a/layout/base/crashtests/1548057.html b/layout/base/crashtests/1548057.html
new file mode 100644
index 0000000000..013d254279
--- /dev/null
+++ b/layout/base/crashtests/1548057.html
@@ -0,0 +1,42 @@
+<!doctype html>
+<html class="reftest-wait">
+<body>
+<script>
+ let parentDoc = `
+ <!doctype html>
+ <script>
+ onload = function() {
+ let observer = new ResizeObserver(_ => {
+ let parentFrame = window.parent.document.querySelector("iframe");
+ parentFrame.getBoundingClientRect();
+ parentFrame.style.display = "none";
+ parentFrame.getBoundingClientRect();
+ parentFrame.srcdoc = ""; // Navigate ourselves away.
+ });
+
+ setTimeout(() => observer.observe(document.documentElement), 0);
+ }
+ </` + 'script>';
+ let iframe = document.createElement("iframe");
+ iframe.srcdoc = parentDoc;
+ iframe.onload = function() {
+ if (this.srcdoc !== "") // We're only interested on the second load.
+ return;
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ SpecialPowers.forceGC();
+ SpecialPowers.forceCC();
+ iframe.style.display = "";
+ requestAnimationFrame(() => {
+ requestAnimationFrame(() => {
+ // If we haven't crashed by now, we should be fine.
+ document.documentElement.className = "";
+ })
+ })
+ })
+ });
+ };
+ document.body.appendChild(iframe);
+</script>
diff --git a/layout/base/crashtests/1549867.html b/layout/base/crashtests/1549867.html
new file mode 100644
index 0000000000..99c44504f9
--- /dev/null
+++ b/layout/base/crashtests/1549867.html
@@ -0,0 +1,13 @@
+<style>
+.a {
+ direction: rtl;
+ columns: 1px;
+ column-span: all
+}
+</style>
+<li style="unicode-bidi: isolate-override" class="a">
+<details style="-webkit-filter: url(#x)" open="">
+<h4 class="a"></h4>
+<time>
+A
+<dialog open="">
diff --git a/layout/base/crashtests/1553874.html b/layout/base/crashtests/1553874.html
new file mode 100644
index 0000000000..5ba2d88059
--- /dev/null
+++ b/layout/base/crashtests/1553874.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <script>
+ function start () {
+ const style = document.createElement('style')
+ document.head.appendChild(style)
+
+ const ul = document.createElement('ul')
+ ul.textContent = '\uFFFD\n'
+ document.documentElement.appendChild(ul)
+
+ style.sheet.insertRule('ul { line-break: anywhere }', (0))
+ }
+ window.addEventListener('load', start)
+ </script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1560328.html b/layout/base/crashtests/1560328.html
new file mode 100644
index 0000000000..358db83ddc
--- /dev/null
+++ b/layout/base/crashtests/1560328.html
@@ -0,0 +1,12 @@
+<html>
+ <head>
+ <title></title>
+ <script>
+ </script>
+ <style>
+ </style>
+ </head>
+ <body>
+ <iframe srcdoc="<script>var c = 0; document.addEventListener('readystatechange', function(e) { if(document.readyState = 'complete') { if (++c == 2) { window.frameElement.remove(); } } });</script>"></iframe>
+ </body>
+</html>
diff --git a/layout/base/crashtests/1566672.html b/layout/base/crashtests/1566672.html
new file mode 100644
index 0000000000..86bde70ed5
--- /dev/null
+++ b/layout/base/crashtests/1566672.html
@@ -0,0 +1,20 @@
+<style>
+html, body, q, ul { columns: 4 }
+.x { height: 600px; }
+</style>
+<script>
+function go() {
+ c.appendChild(d)
+}
+</script>
+<body onload=go()>
+ <q id="c" style="word-break: break-all">
+ <form hidden="hidden">
+ <output id="d">
+ <ul class="x">
+ aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+ </ul>
+ </output>
+ </form>
+ </q>
+</body>
diff --git a/layout/base/crashtests/1574101-1.html b/layout/base/crashtests/1574101-1.html
new file mode 100644
index 0000000000..c4865ff23e
--- /dev/null
+++ b/layout/base/crashtests/1574101-1.html
@@ -0,0 +1,16 @@
+<script>
+function start() {
+ document.documentElement.style.transform='scale(0.00001)';
+ o219=document.createElement('hr');
+ o219.style.display='ruby';
+ o637=document.createElement('summary');
+ o637.appendChild(o219);
+ o663=document.createElement('details');
+ o663.appendChild(o637);
+ document.documentElement.appendChild(o663);
+ o219.style.position='absolute';
+ o866=document.documentElement.getBoxQuads();
+ o663.style.position='fixed';
+}
+</script>
+<body onload="start()"></body>
diff --git a/layout/base/crashtests/1574101-2.html b/layout/base/crashtests/1574101-2.html
new file mode 100644
index 0000000000..0a0b96c03e
--- /dev/null
+++ b/layout/base/crashtests/1574101-2.html
@@ -0,0 +1,10 @@
+<script>
+function go() {
+ a.replaceChild(b, b)
+}
+</script>
+<body onload=go()>
+<p id="a">
+<menuitem id="b">
+<ruby style="position: absolute">
+a
diff --git a/layout/base/crashtests/1575908-1.html b/layout/base/crashtests/1575908-1.html
new file mode 100644
index 0000000000..0edeb2ddbc
--- /dev/null
+++ b/layout/base/crashtests/1575908-1.html
@@ -0,0 +1,32 @@
+<html>
+<head>
+ <style>
+ HTML, .class_1 {
+ margin-block-start: 65%;
+ text-decoration: line-through blink overline underline;
+ }
+
+ .class_1 {
+ all: initial;
+ word-spacing: -27777ex;
+ text-align: justify ! important;
+ scale: 68;
+ text-rendering: geometricPrecision;
+ }
+
+ </style>
+ <script>
+ function start () {
+ const map = document.createElement('map')
+ const textarea = document.createElement('textarea')
+ textarea.setAttribute('class', 'class_1')
+ textarea.value = 'ã™ß²ó ”©Ù©Ù ð…¯^=0]Ù«f󠆮⡛󠗸𯴋󠇬󠷠%Û°ó Šê£­áˆ¯â¡c椕ãžð¯†ó ¾·ï‹“9Nᷙ𯷘ð›Šð¼‘\\wÙ«â¡â€¬%=â„٩𪡢҃�'
+ textarea.selectionStart = 128
+ document.documentElement.appendChild(map)
+ map.insertAdjacentElement('afterbegin', textarea)
+ }
+
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+</html>
diff --git a/layout/base/crashtests/1576972-1.html b/layout/base/crashtests/1576972-1.html
new file mode 100644
index 0000000000..8f20f3b727
--- /dev/null
+++ b/layout/base/crashtests/1576972-1.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+ <style>
+ * {
+ quotes: none !important;
+ }
+ </style>
+ <script>
+ function start () {
+ const o1 = document.getElementById('id_1')
+ const o2 = document.createElement('q')
+ const o3 = document.createElement('v')
+ document.documentElement.appendChild(o1)
+ o3.dir = 'rtl'
+ o2.appendChild(o3)
+ document.documentElement.appendChild(o2)
+ }
+
+ document.addEventListener('DOMContentLoaded', start)
+ </script>
+</head>
+<q id="id_1">
+</html>
diff --git a/layout/base/crashtests/1578844-1.html b/layout/base/crashtests/1578844-1.html
new file mode 100644
index 0000000000..7c23e3a0b7
--- /dev/null
+++ b/layout/base/crashtests/1578844-1.html
@@ -0,0 +1,14 @@
+<script>
+function go() {
+ b.appendChild(a)
+}
+</script>
+<body onload=go()>
+<data>
+<dl id="a" style="display: contents">
+<dl></dl>
+<dd style="position: fixed">
+</dl>
+<svg>
+<text>
+<textPath id="b">
diff --git a/layout/base/crashtests/1578844-2.html b/layout/base/crashtests/1578844-2.html
new file mode 100644
index 0000000000..e1750a27d9
--- /dev/null
+++ b/layout/base/crashtests/1578844-2.html
@@ -0,0 +1,20 @@
+<style>
+ dl::before {
+ position: fixed;
+ content: "";
+ }
+</style>
+<script>
+function go() {
+ b.appendChild(a)
+}
+</script>
+<body onload=go()>
+<data>
+<dl id="a" style="display: contents">
+<dl></dl>
+<dd style="position: fixed">
+</dl>
+<svg>
+<text>
+<textPath id="b">
diff --git a/layout/base/crashtests/1579953-1.html b/layout/base/crashtests/1579953-1.html
new file mode 100644
index 0000000000..92671e0561
--- /dev/null
+++ b/layout/base/crashtests/1579953-1.html
@@ -0,0 +1,16 @@
+<style>
+details::first-letter {}
+* { column-count: 1 }
+</style>
+<script>
+function go() {
+ a.appendChild(document.createElement("s"))
+ b.createTBody()
+}
+</script>
+<body onload=go()>
+<table id="b"></table>
+<details id="a" open>
+<summary hidden></summary>
+U
+</details>
diff --git a/layout/base/crashtests/1580576.html b/layout/base/crashtests/1580576.html
new file mode 100644
index 0000000000..b41bf3f4e4
--- /dev/null
+++ b/layout/base/crashtests/1580576.html
@@ -0,0 +1,11 @@
+<style>
+:not(xmp) { column-span: all }
+</style>
+<script>
+window.onload = () => {
+ a.appendChild(b)
+}
+</script>
+<details id="a" style="column-width: 0px">
+<summary>
+<label id="b">
diff --git a/layout/base/crashtests/1586600.html b/layout/base/crashtests/1586600.html
new file mode 100644
index 0000000000..c825c5c85e
--- /dev/null
+++ b/layout/base/crashtests/1586600.html
@@ -0,0 +1,5 @@
+<html style="overscroll-behavior-y: contain;">
+<body style="overflow: hidden;">
+<script>
+ document.body.clientWidth;
+</script>
diff --git a/layout/base/crashtests/1599518.html b/layout/base/crashtests/1599518.html
new file mode 100644
index 0000000000..15d55a86dc
--- /dev/null
+++ b/layout/base/crashtests/1599518.html
@@ -0,0 +1,9 @@
+<script>
+document.addEventListener('DOMContentLoaded', function() {
+ var x = new ResizeObserver(function(a1, a2) { })
+ var a = document.createElementNS('http://www.w3.org/1999/xhtml', 'l')
+ x.observe(a, { })
+ x.disconnect()
+ x.observe(a, { })
+})
+</script>
diff --git a/layout/base/crashtests/1599532.html b/layout/base/crashtests/1599532.html
new file mode 100644
index 0000000000..3b60edf7c5
--- /dev/null
+++ b/layout/base/crashtests/1599532.html
@@ -0,0 +1 @@
+<meta http-equiv='content-language' content='fr-È'>
diff --git a/layout/base/crashtests/1606492.html b/layout/base/crashtests/1606492.html
new file mode 100644
index 0000000000..3477be3583
--- /dev/null
+++ b/layout/base/crashtests/1606492.html
@@ -0,0 +1,21 @@
+<script>
+function go() {
+ var a = document.createElement("e")
+ document.body.appendChild(a)
+ a.addEventListener("DOMSubtreeModified", () => {
+ c.src = ""
+ d.replaceWith(b)
+ f.srcdoc = ""
+ })
+ document.execCommand("selectAll", false)
+ a.setAttribute("s", "")
+}
+</script>
+<div>
+<output id="b"></output>
+<iframe id="c" sandbox="allow-same-origin"></iframe>
+<ul contenteditable="true">
+<li id="d">A</li>
+</ul>
+<iframe id="f"></iframe>
+<audio onloadstart="go()" src="">
diff --git a/layout/base/crashtests/1654315.html b/layout/base/crashtests/1654315.html
new file mode 100644
index 0000000000..9240566e6d
--- /dev/null
+++ b/layout/base/crashtests/1654315.html
@@ -0,0 +1,13 @@
+<style>
+:not(svg) { -webkit-mask: url(#x); }
+</style>
+<script>
+function go() {
+ a.style.setProperty("content", "url(")
+ a.style.setProperty("position", "fixed")
+ a.style.setProperty("display", "-webkit-inline-flex")
+ a.style.setProperty("filter", "brightness(")
+}
+</script>
+<body onload=go()>
+<link id="a">x</link>
diff --git a/layout/base/crashtests/1676301-1.html b/layout/base/crashtests/1676301-1.html
new file mode 100644
index 0000000000..9ca79823f8
--- /dev/null
+++ b/layout/base/crashtests/1676301-1.html
@@ -0,0 +1,16 @@
+<style>
+#a { max-width: 1 }
+</style>
+<script>
+function go() {
+ a.value = ">"
+ a.selectionDirection = "foo"
+ b.requestFullscreen()
+ document.createElement("body").onresize = () => { a.selectionEnd = 0 }
+ setTimeout(window.close, 100)
+}
+</script>
+<body onload=go()>
+<textarea id="a"></textarea>
+<ol id="b">
+<div>
diff --git a/layout/base/crashtests/1685146.html b/layout/base/crashtests/1685146.html
new file mode 100644
index 0000000000..e6804f07dc
--- /dev/null
+++ b/layout/base/crashtests/1685146.html
@@ -0,0 +1,17 @@
+<style>
+.a {
+ clip-path: url(#x);
+ -webkit-filter: grayscale(0);
+ border-right-style: dashed;
+}
+* {
+ outline-style: solid;
+}
+.b {
+ translate: 0px 0px;
+ overflow-y: scroll;
+}
+</style>
+<marquee height="0" class="b">
+<ins class="a">
+
diff --git a/layout/base/crashtests/1689371.html b/layout/base/crashtests/1689371.html
new file mode 100644
index 0000000000..3220b1ee46
--- /dev/null
+++ b/layout/base/crashtests/1689371.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<div style="height: 200vh;"></div>
+</html>
diff --git a/layout/base/crashtests/1689912.html b/layout/base/crashtests/1689912.html
new file mode 100644
index 0000000000..522b8b414d
--- /dev/null
+++ b/layout/base/crashtests/1689912.html
@@ -0,0 +1,19 @@
+<fieldset>
+<fieldset></fieldset>
+<legend></legend>
+</fieldset>
+
+<fieldset>
+<fieldset style="overflow-x:auto"></fieldset>
+<legend></legend>
+</fieldset>
+
+<fieldset>
+<fieldset style="overflow-x:auto; columns:2"></fieldset>
+<legend></legend>
+</fieldset>
+
+<fieldset>
+<fieldset style="columns:2"></fieldset>
+<legend></legend>
+</fieldset>
diff --git a/layout/base/crashtests/1690163.html b/layout/base/crashtests/1690163.html
new file mode 100644
index 0000000000..4251dc3b3d
--- /dev/null
+++ b/layout/base/crashtests/1690163.html
@@ -0,0 +1,7 @@
+<script>
+window.onload = () => {
+ document.getElementById('a').appendChild(document.createElement('col'))
+}
+</script>
+<fieldset id='a'>
+<legend>
diff --git a/layout/base/crashtests/1723200.html b/layout/base/crashtests/1723200.html
new file mode 100644
index 0000000000..75880dd12b
--- /dev/null
+++ b/layout/base/crashtests/1723200.html
@@ -0,0 +1,22 @@
+<!-- x -->
+<style>
+.a {
+ filter: url(#x);
+ overflow: scroll;
+}
+.b {
+ overflow-y: scroll;
+ font: 1px/0 Sherif;
+ font-size-adjust: 3;
+ -webkit-transform-style: preserve-3d;
+}
+* {
+ transform-style: inherit;
+}
+</style>
+<li class="b">
+<dialog class="b" open="true">x</dialog>
+<dir>
+<li class="a">
+<!-- x -->
+
diff --git a/layout/base/crashtests/1729578.html b/layout/base/crashtests/1729578.html
new file mode 100644
index 0000000000..27dd8a140f
--- /dev/null
+++ b/layout/base/crashtests/1729578.html
@@ -0,0 +1,26 @@
+<style>
+#b {
+ column-span: all;
+}
+* {
+ column-count: 17;
+}
+.c {
+ column-span: all;
+ overflow-wrap: break-word;
+ height: 0.001592530849548246em;
+}
+</style>
+<script>
+let go = () => {
+ a.appendChild(d)
+}
+</script>
+<body onload=go()>
+<ol id="b">
+<li></li>
+</ol>
+<a id="a">x</a>
+<dl id="d">
+<dd class="c">;MLCI|=oV;nvAP*o7U</dd>
+<u>x</u>
diff --git a/layout/base/crashtests/1729581.html b/layout/base/crashtests/1729581.html
new file mode 100644
index 0000000000..0598f2c1a8
--- /dev/null
+++ b/layout/base/crashtests/1729581.html
@@ -0,0 +1,12 @@
+<style>
+* {
+ clip-path: polygon(1px 1px, 1px -1px, 0px 1px);
+}
+.a {
+ position: fixed;
+ -webkit-transform: translatez(0);
+ opacity: 0;
+}
+</style>
+<hr class="a">Z.Vlz|D&lt;|&#x27;mFZ&lt;M</hr>
+
diff --git a/layout/base/crashtests/1734007.html b/layout/base/crashtests/1734007.html
new file mode 100644
index 0000000000..6d52d1b2ae
--- /dev/null
+++ b/layout/base/crashtests/1734007.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ clip-path: fill-box circle(3407268870.1142473cm at left 76% top 53%);
+ border: hidden thick hsla(0.8914928662981693rad, 5.18832867065233 e+307%, 81%, 1%);
+ opacity: 1%;
+ }
+ </style>
+</head>
+<table>
+ <caption style="position: fixed !important;" contenteditable="true"></caption>
+</table>
+</html>
diff --git a/layout/base/crashtests/1745860.html b/layout/base/crashtests/1745860.html
new file mode 100644
index 0000000000..7eb1bb9141
--- /dev/null
+++ b/layout/base/crashtests/1745860.html
@@ -0,0 +1,14 @@
+<script>
+let go = () => {
+ d.appendChild(b)
+ c.src = "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="
+}
+window.onload = go
+</script>
+<map id="a">
+<area id="b"></area>
+<video src="data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch4ECQoWBAhhTgGcBAAAAAAAB6BFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsggHL7AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUq17GDD0JATYCNTGF2ZjU3LjI5LjEwMVdBjUxhdmY1Ny4yOS4xMDFzpJBAb17Yv2oNAF1ZEESuco33RImIQFCAAAAAAAAWVK5rAQAAAAAAADyuAQAAAAAAADPXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDmDgQEj44OEAfygVeABAAAAAAAAB7CCAUC6gfAfQ7Z1AQAAAAAAAEfngQCjqYEAAICCSYNCABPwDvYAOCQcGFQAAFBh9jAAABML7AAATEnjdRwIJ+gAo5eBACEAhgBAkpwATEAABCasAABekcXgABxTu2sBAAAAAAAAEbuPs4EAt4r3gQHxggF48IED">
+</video>
+<img id="c" usemap="#a"></img>
+<video id="d" onloadstart="go()">
+<source></source>
diff --git a/layout/base/crashtests/1746989.html b/layout/base/crashtests/1746989.html
new file mode 100644
index 0000000000..398447b77c
--- /dev/null
+++ b/layout/base/crashtests/1746989.html
@@ -0,0 +1,11 @@
+<style>
+* {
+ rotate: 93deg 6 0 -1;
+}
+</style>
+<script>
+window.onload = () => {
+ document.execCommand("selectAll", false);
+}
+</script>
+<textarea>a</textarea>
diff --git a/layout/base/crashtests/1747277-1.html b/layout/base/crashtests/1747277-1.html
new file mode 100644
index 0000000000..21e512d096
--- /dev/null
+++ b/layout/base/crashtests/1747277-1.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<script>
+window.requestIdleCallback(() => {
+ setTimeout(() => {
+ let docElement = document.documentElement;
+ window.close();
+ docElement.removeAttribute('class');
+ }, 500);
+}, { timeout: 1000 });
+</script>
+<script>
+function go() {
+ window.top.requestAnimationFrame(() => {
+ SpecialPowers.wrap(window).printPreview()?.close()
+ })
+}
+</script>
+<body onload=go()>
+<embed src="data:video/webm;base64,GkXfowEAAAAAAAAfQoaBAUL3gQFC8oEEQvOBCEKChHdlYm1Ch4ECQoWBAhhTgGcBAAAAAAAB6BFNm3RALE27i1OrhBVJqWZTrIHfTbuMU6uEFlSua1OsggEwTbuMU6uEHFO7a1OsggHL7AEAAAAAAACkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVSalmAQAAAAAAAEUq17GDD0JATYCNTGF2ZjU3LjI5LjEwMVdBjUxhdmY1Ny4yOS4xMDFzpJBAb17Yv2oNAF1ZEESuco33RImIQFCAAAAAAAAWVK5rAQAAAAAAADyuAQAAAAAAADPXgQFzxYEBnIEAIrWcg3VuZIaFVl9WUDmDgQEj44OEAfygVeABAAAAAAAAB7CCAUC6gfAfQ7Z1AQAAAAAAAEfngQCjqYEAAICCSYNCABPwDvYAOCQcGFQAAFBh9jAAABML7AAATEnjdRwIJ+gAo5eBACEAhgBAkpwATEAABCasAABekcXgABxTu2sBAAAAAAAAEbuPs4EAt4r3gQHxggF48IED">
+</body>
+</html>
diff --git a/layout/base/crashtests/1752649.html b/layout/base/crashtests/1752649.html
new file mode 100644
index 0000000000..120e3491ba
--- /dev/null
+++ b/layout/base/crashtests/1752649.html
@@ -0,0 +1,4 @@
+<style>
+:first-of-type::before { position: fixed }
+</style>
+<q>
diff --git a/layout/base/crashtests/1753779.html b/layout/base/crashtests/1753779.html
new file mode 100644
index 0000000000..d146f913fe
--- /dev/null
+++ b/layout/base/crashtests/1753779.html
@@ -0,0 +1,16 @@
+<style>
+#a {
+ line-height: 41%;
+ flex: -1; transform: translatex(0)
+}
+.b {
+ overflow-y: scroll;
+ opacity: -1;
+}
+</style>
+<h1 id="a">
+<ol class="b">
+<li>
+<label class="b">
+<menu style="position: fixed">x</menu>
+
diff --git a/layout/base/crashtests/1755790.html b/layout/base/crashtests/1755790.html
new file mode 100644
index 0000000000..da15091a8b
--- /dev/null
+++ b/layout/base/crashtests/1755790.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ *,
+ #id_0 {
+ position: fixed;
+ border-inline-style: groove hidden;
+ opacity: 0% ! important;
+ border-bottom-style: groove ! important;
+ backdrop-filter: opacity(44%);
+ }
+
+ HTML {
+ -webkit-mask-image: url(0062be2b5ec34515f5de93ee618142001c6f4728.icc);
+ }
+
+ * {
+ all: revert;
+ }
+ </style>
+ <script>
+ document.addEventListener("DOMContentLoaded", () => {
+ const slot = document.createElement("slot")
+ document.documentElement.appendChild(slot)
+ slot.setAttribute("id", "id_0")
+ setTimeout(() => (slot.style.display = "block"), 50)
+ })
+ </script>
+</head>
+</html>
diff --git a/layout/base/crashtests/176915-1.html b/layout/base/crashtests/176915-1.html
new file mode 100644
index 0000000000..8b83a3e0e0
--- /dev/null
+++ b/layout/base/crashtests/176915-1.html
@@ -0,0 +1,10 @@
+<html>
+ <head>
+ <title>bug 176915</title>
+ </head>
+ <body>
+ <div style='position:relative;display:inline'>
+ <object style='position:absolute;'></object>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/1771503.html b/layout/base/crashtests/1771503.html
new file mode 100644
index 0000000000..de7c4990fb
--- /dev/null
+++ b/layout/base/crashtests/1771503.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ transform: matrix3d(130, -7052, 1000, 35803, 122, 7197, 197, 126, 201, 64, 38, -69, 5.727476671737168, 124, 22882, 168.04863081346616);
+ overflow: auto clip ! important;
+ padding-right: 39%;
+ min-inline-size: min-content;
+ }
+ </style>
+</head>
+<body>
+<table>
+ <th>
+ <fieldset>
+ <textarea cols='4096' autofocus></textarea>
+ </fieldset>
+ </th>
+</table>
+</body>
+</html>
diff --git a/layout/base/crashtests/1789934.html b/layout/base/crashtests/1789934.html
new file mode 100644
index 0000000000..91a80fc039
--- /dev/null
+++ b/layout/base/crashtests/1789934.html
@@ -0,0 +1,16 @@
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ SpecialPowers.wrap(window).printPreview()?.close();
+})
+</script>
+<style>
+:only-child {
+ page-break-after: right;
+}
+:only-child::after {
+ position: fixed;
+}
+</style>
+<pre>
+<q>
+<embed src='data:application/xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPGEvPg=='>
diff --git a/layout/base/crashtests/1791883.html b/layout/base/crashtests/1791883.html
new file mode 100644
index 0000000000..a127ba5758
--- /dev/null
+++ b/layout/base/crashtests/1791883.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ scroll-snap-type: y mandatory;
+ scroll-snap-align: none end;
+ }
+ </style>
+ <script>
+ document.addEventListener('DOMContentLoaded', () => {
+ document.documentElement.contentEditable = true
+ document.execCommand('insertHorizontalRule', false, null)
+ })
+ </script>
+</head>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/1797995.html b/layout/base/crashtests/1797995.html
new file mode 100644
index 0000000000..1865f3c9e9
--- /dev/null
+++ b/layout/base/crashtests/1797995.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ * {
+ position: fixed !important;
+ writing-mode: sideways-rl;
+ overflow-y: hidden;
+ resize: vertical;
+ }
+ </style>
+</head>
+</html>
diff --git a/layout/base/crashtests/1818036.html b/layout/base/crashtests/1818036.html
new file mode 100644
index 0000000000..da5c974085
--- /dev/null
+++ b/layout/base/crashtests/1818036.html
@@ -0,0 +1,13 @@
+<style>
+*:defined { margin-top: 98vmax }
+</style>
+<script>
+document.addEventListener('DOMContentLoaded', () => {
+ try { a.length = 2 } catch (e) {}
+ document.execCommand("selectAll", false, null)
+ try { a.size = 31 } catch (e) {}
+})
+</script>
+<select id="a" dir="rtl"></select>
+<keygen autofocus="" contenteditable="true">
+AAAA
diff --git a/layout/base/crashtests/1819239.html b/layout/base/crashtests/1819239.html
new file mode 100644
index 0000000000..3dd31acbfb
--- /dev/null
+++ b/layout/base/crashtests/1819239.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
+<script>
+document.addEventListener("DOMContentLoaded", () => {
+ a.addEventListener("DOMNodeInserted", () => {
+ document.designMode = "off"
+ d.setAttribute("contenteditable", "true")
+ b.scrollBy({ })
+ window.parent.scroll(0, 0)
+ })
+ document.designMode = "on"
+ c.scrollIntoView()
+ document.execCommand("selectAll", false, null)
+ document.execCommand("foreColor", false, "rgb(149,240,138)")
+})
+</script>
+<output id="a"></output>
+<svg height="51em">
+<line id="b"></line>
+<a id="c">
+</svg>
+<del id="d">
diff --git a/layout/base/crashtests/1821469.html b/layout/base/crashtests/1821469.html
new file mode 100644
index 0000000000..568a7a5101
--- /dev/null
+++ b/layout/base/crashtests/1821469.html
@@ -0,0 +1,4 @@
+<meta charset="UTF-8">
+Ø·
+<select>
+<option label='&#x1E'>
diff --git a/layout/base/crashtests/1849898-1.html b/layout/base/crashtests/1849898-1.html
new file mode 100644
index 0000000000..a91a97f7ce
--- /dev/null
+++ b/layout/base/crashtests/1849898-1.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<script>
+let pp;
+window.addEventListener("MozReftestInvalidate", finish);
+function go() {
+ let c = document.createElement("frameset");
+ document.documentElement.appendChild(c);
+
+ document.onscroll = () => {
+ try {
+ pp = SpecialPowers.wrap(window).printPreview();
+ pp?.close();
+ } catch (e) {}
+
+ setTimeout(finish,0);
+ };
+ window.scrollBy(0, 10);
+}
+
+window.addEventListener("load", go);
+
+let num = 0;
+function finish() {
+ num++;
+ if (num < 2) {
+ return;
+ }
+
+ setTimeout(function() {
+ try {
+ pp?.close();
+ } catch (e) {}
+
+ document.documentElement.removeAttribute("class");
+ }, 0);
+}
+</script>
+</html>
diff --git a/layout/base/crashtests/191272-1.html b/layout/base/crashtests/191272-1.html
new file mode 100644
index 0000000000..6adac07896
--- /dev/null
+++ b/layout/base/crashtests/191272-1.html
@@ -0,0 +1,13 @@
+<html>
+<head>
+<style>
+p:first-letter {
+ position: fixed;
+ left: 100px;
+ top: 100px;
+}
+</style>
+<body>
+<p>Blah blah blah
+</body>
+</html>
diff --git a/layout/base/crashtests/199696-1.html b/layout/base/crashtests/199696-1.html
new file mode 100644
index 0000000000..f50fc487dd
--- /dev/null
+++ b/layout/base/crashtests/199696-1.html
@@ -0,0 +1,33 @@
+<html>
+<head><title>bug 22037</title>
+
+ <!-- got the testcase from /mozilla/layout/html/tests/block/bugs/ -->
+
+</head>
+
+<body>
+
+
+<p><span><span><span>
+before before before before before before before before
+before before before before before before before before before before before
+before before before before before before before before before before before
+before before before before before before before before before before before before
+ <object src="foo">
+ left left left left left left left left left left left left left left
+ left left left left
+ <h2>
+ block block block block block block block block block block block block block
+ block block block block block block block
+ </h2>
+ right right right right right right right right right right right right right right right
+ right right right
+ </object>
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+after after after after after after after after after after after after after after after
+</span></span></span></p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/217903-1.html b/layout/base/crashtests/217903-1.html
new file mode 100644
index 0000000000..e6d308504a
--- /dev/null
+++ b/layout/base/crashtests/217903-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<li>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/223064-1.html b/layout/base/crashtests/223064-1.html
new file mode 100644
index 0000000000..e72ceda88d
--- /dev/null
+++ b/layout/base/crashtests/223064-1.html
@@ -0,0 +1,11 @@
+<!doctype html public "-//W3C//DTD HTML 4.01 Transitional//EN">
+
+<html>
+<body>
+
+<script language="JavaScript" type="text/javascript">
+ document.writeln("<A><DIV STYLE=\"position:absolute;\">" + "</DIV></A>");
+</script>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/234851-1.html b/layout/base/crashtests/234851-1.html
new file mode 100644
index 0000000000..56c3f37956
--- /dev/null
+++ b/layout/base/crashtests/234851-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<head>
+<title>Testcase</title>
+<style type="text/css">
+ html{
+ overflow:scroll;
+ }
+</style>
+</head>
+
+<body onload="var sheet = document.styleSheets[0]; sheet.disabled = true; sheet.disabled = false;">
+ Load this page to crash
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/234851-2.html b/layout/base/crashtests/234851-2.html
new file mode 100644
index 0000000000..ee17908511
--- /dev/null
+++ b/layout/base/crashtests/234851-2.html
@@ -0,0 +1,35 @@
+<html style="overflow:scroll">
+<body>
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+<p>Dum de doo
+</body>
+</html>
diff --git a/layout/base/crashtests/241300-1.html b/layout/base/crashtests/241300-1.html
new file mode 100644
index 0000000000..5eb71ac9e6
--- /dev/null
+++ b/layout/base/crashtests/241300-1.html
@@ -0,0 +1,5 @@
+<html><head></head>
+<body background="cid:00d201c264d0$feb75c80$0300a8c0@node3">
+</body>
+</html>
+
diff --git a/layout/base/crashtests/243159-1.html b/layout/base/crashtests/243159-1.html
new file mode 100644
index 0000000000..94c2df5e9f
--- /dev/null
+++ b/layout/base/crashtests/243159-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML PUBLIC "" []>
+<p style="display: table; white-space: nowrap; width: 400px; height: 100px">
+ <input type="text" style="display: table-cell;">
+ </p> \ No newline at end of file
diff --git a/layout/base/crashtests/243159-2.xhtml b/layout/base/crashtests/243159-2.xhtml
new file mode 100644
index 0000000000..79d9bcd90a
--- /dev/null
+++ b/layout/base/crashtests/243159-2.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+ <body onload="run()">
+ <mathml:math id="test" style="display: block">
+ </mathml:math>
+<script>
+ function run() {
+ var t1 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtable");
+ var t2 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtable");
+ var r1 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtr");
+ var r2 = document.createElementNS("http://www.w3.org/1998/Math/MathML",
+ "mtr");
+ var test =
+ document.getElementsByTagNameNS("http://www.w3.org/1998/Math/MathML", "math")[0];
+ t1.appendChild(r1);
+ test.appendChild(t1);
+ test.appendChild(t2);
+ t2.appendChild(r2);
+
+ }
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/243519-1.html b/layout/base/crashtests/243519-1.html
new file mode 100644
index 0000000000..2652415734
--- /dev/null
+++ b/layout/base/crashtests/243519-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+ <div style="position:absolute;">Hello</div>
+ <div style="position:fixed;">Kitty</div>
+ <script>
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+ document.body.offsetTop;
+
+ document.documentElement.style.position = "absolute";
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+ document.body.offsetTop;
+
+ document.documentElement.style.position = "fixed";
+ document.body.offsetTop;
+ document.documentElement.style.display = "table";
+ document.body.offsetTop;
+ document.documentElement.style.display = "";
+
+ document.documentElement.style.position = "";
+ document.body.offsetTop;
+ </script>
+</body>
+</html>
diff --git a/layout/base/crashtests/244490-1.html b/layout/base/crashtests/244490-1.html
new file mode 100644
index 0000000000..366b03a1ff
--- /dev/null
+++ b/layout/base/crashtests/244490-1.html
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
+ "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xml:lang="de" xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Crash Test</title>
+ <base href="D:\CSS Test Files\" />
+ <style type="text/css">
+ p { border: 1px red solid }
+ p:before { content: url("images/quote_end.png") }
+ </style>
+ </head>
+ <body>
+ <p>Did it crash?</p>
+ </body>
+</html>
diff --git a/layout/base/crashtests/254367-1.html b/layout/base/crashtests/254367-1.html
new file mode 100644
index 0000000000..68b6acd43a
--- /dev/null
+++ b/layout/base/crashtests/254367-1.html
@@ -0,0 +1,6 @@
+<!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 254367</title>
+</head>
+<body>text<img> </body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/263359-1.html b/layout/base/crashtests/263359-1.html
new file mode 100644
index 0000000000..cddd81b840
--- /dev/null
+++ b/layout/base/crashtests/263359-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta content="text/html; charset=UTF-8" http-equiv="Content-Type">
+ <title>CSS Writing Modes Module Level 3</title>
+ <script type="text/javascript">
+function boom() {
+ document.getElementById("example").style.fontSize = "larger";
+}
+ </script>
+ </head>
+ <body onload=boom()>
+ <div id="example">
+ <p>×</p>
+ <pre><code>
+&lt;HEBREW&gt;
+ &lt;PAR&gt;HEBREW1 HEBREW2 english3 HEBREW4 HEBREW5&lt;/PAR&gt;
+ &lt;PAR&gt;HEBREW6 &lt;EMPH&gt;HEBREW7&lt;/EMPH&gt; HEBREW8&lt;/PAR&gt;
+&lt;/HEBREW&gt;
+&lt;ENGLISH&gt;
+ &lt;PAR&gt;english9 english10 english11 HEBREW12 HEBREW13&lt;/PAR&gt;
+ &lt;PAR&gt;english14 english15 english16&lt;/PAR&gt;
+ &lt;PAR&gt;english17 &lt;HE-QUO&gt;HEBREW18 english19 HEBREW20&lt;/HE-QUO&gt;&lt;/PAR&gt;
+&lt;/ENGLISH&gt;
+ </code></pre>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/265027-1.html b/layout/base/crashtests/265027-1.html
new file mode 100644
index 0000000000..9b455da41a
--- /dev/null
+++ b/layout/base/crashtests/265027-1.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+<MARQUEE>
+<TABLE>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<MARQUEE HEIGHT=100000000>
+<TBODY>
+Attack of the marquees!
+
+
diff --git a/layout/base/crashtests/265736-1.html b/layout/base/crashtests/265736-1.html
new file mode 100644
index 0000000000..cecea66fdf
--- /dev/null
+++ b/layout/base/crashtests/265736-1.html
@@ -0,0 +1,2 @@
+<HTML>
+<HR WIDTH=4444444 COLOR="#000000"> \ No newline at end of file
diff --git a/layout/base/crashtests/265736-2.html b/layout/base/crashtests/265736-2.html
new file mode 100644
index 0000000000..2e5041b2dd
--- /dev/null
+++ b/layout/base/crashtests/265736-2.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+
+<body>
+<iframe style="border-top-width: 31378748; border-bottom-right-radius: 23895784; ">
+</body>
+</html>
diff --git a/layout/base/crashtests/265899-1.html b/layout/base/crashtests/265899-1.html
new file mode 100644
index 0000000000..e2fb197a1b
--- /dev/null
+++ b/layout/base/crashtests/265899-1.html
@@ -0,0 +1,5 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY STYLE="float:right; HEIGHT:0pt; PADDING:99999999999px;"></BODY>
+</HTML>
diff --git a/layout/base/crashtests/265973-1.html b/layout/base/crashtests/265973-1.html
new file mode 100644
index 0000000000..2ded7fb410
--- /dev/null
+++ b/layout/base/crashtests/265973-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<DIV STYLE="MARGIN:-99999999999px; PADDING:99999999999px; float:left; HEIGHT:0;"></DIV>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/265986-1.html b/layout/base/crashtests/265986-1.html
new file mode 100644
index 0000000000..8d4ca290f5
--- /dev/null
+++ b/layout/base/crashtests/265986-1.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<IFRAME STYLE="MARGIN:99999999999px; PADDING:-99999999999px;"></IFRAME>
+<APPLET STYLE="HEIGHT:9999999999pt; float:left; MARGIN:-99999999999px; border:99999999999px solid blue;"></APPLET>
+<MARQUEE STYLE=" WIDTH:9999999999px;">W</MARQUEE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/265999-1.html b/layout/base/crashtests/265999-1.html
new file mode 100644
index 0000000000..7e6e3d4162
--- /dev/null
+++ b/layout/base/crashtests/265999-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<MARQUEE STYLE="HEIGHT:9999999999px; float:right; border:99999999999px solid blue;"></MARQUEE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266222-1.html b/layout/base/crashtests/266222-1.html
new file mode 100644
index 0000000000..0079a6b8a5
--- /dev/null
+++ b/layout/base/crashtests/266222-1.html
@@ -0,0 +1,7 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<NOFRAMES STYLE="DISPLAY:BLOCK; float:left; overflow:inherit;"></NOFRAMES>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266360-1.html b/layout/base/crashtests/266360-1.html
new file mode 100644
index 0000000000..30bdbb65bd
--- /dev/null
+++ b/layout/base/crashtests/266360-1.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE=" border:10391122102cm solid #FFFFFF; float:right;">
+<SPAN STYLE=" border:inherit;"></SPAN>
+<H1 STYLE="float:right; HEIGHT:613927841cm; border:inherit;">Test</H1>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266445-1.html b/layout/base/crashtests/266445-1.html
new file mode 100644
index 0000000000..1d79327d53
--- /dev/null
+++ b/layout/base/crashtests/266445-1.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE="overflow:hidden;">
+<HR STYLE="float:right; padding:71155995130em;">
+<OL STYLE="position:static;"><LI>Test</LI></OL>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/266445-2.html b/layout/base/crashtests/266445-2.html
new file mode 100644
index 0000000000..4de4e740b1
--- /dev/null
+++ b/layout/base/crashtests/266445-2.html
@@ -0,0 +1,9 @@
+<HTML>
+<HEAD>
+</HEAD>
+<BODY>
+<BODY STYLE="overflow:hidden;">
+<HR STYLE="float:right; height:2px; padding:71155995130em;">
+<OL STYLE="position:static;"><LI>Test</LI></OL>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/268157-1.html b/layout/base/crashtests/268157-1.html
new file mode 100644
index 0000000000..5bdc494c6f
--- /dev/null
+++ b/layout/base/crashtests/268157-1.html
@@ -0,0 +1,15 @@
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8
+<object>
+<div>
+</div>
+</object>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8
+
+<span>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+<object>
+<div>
+</div>
+</object>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+</span>
diff --git a/layout/base/crashtests/269566-1.html b/layout/base/crashtests/269566-1.html
new file mode 100644
index 0000000000..35c63bcb19
--- /dev/null
+++ b/layout/base/crashtests/269566-1.html
@@ -0,0 +1,11 @@
+<html><head>
+<style>
+BODY { display:table; }
+</style>
+</head>
+<body>
+<div><iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%3E%3C/body%3E%3C/html%3E"></iframe>
+</div>
+</body></html>
+
+
diff --git a/layout/base/crashtests/272647-1.html b/layout/base/crashtests/272647-1.html
new file mode 100644
index 0000000000..f2fa5f2ea7
--- /dev/null
+++ b/layout/base/crashtests/272647-1.html
@@ -0,0 +1,18 @@
+<html>
+ <header>
+ <title>Defects </title>
+ </header>
+<body>
+<center><table>
+<caption>
+</caption>
+
+<p>
+<caption>
+</tr></td>
+</center>
+<center><table>
+<td><tr>
+delete me and the problem goes away
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/275746-1.html b/layout/base/crashtests/275746-1.html
new file mode 100644
index 0000000000..ea15adae1a
--- /dev/null
+++ b/layout/base/crashtests/275746-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html><head><title>Testcase bug 275746 - Crash when clicking in drop down list, when changing from display:table-cell to display:inline</title>
+<style>
+span,select{display:table-cell;}
+</style>
+</head>
+<body onload="document.getElementById('x').style.display = 'inline'; document.documentElement.className = '';">
+<span>This is needed</span><select id='x'><option>option 1</option><option>option 2</option></select>
+</body></html>
diff --git a/layout/base/crashtests/276053-1.html b/layout/base/crashtests/276053-1.html
new file mode 100644
index 0000000000..3155f0857a
--- /dev/null
+++ b/layout/base/crashtests/276053-1.html
@@ -0,0 +1,21 @@
+<html><head><title>Testcase bug 276053 - Closeing a tab with http://linuxblog.sytes.net loaded in it causes Firefox to crash [@ nsView::GetDimensions]</title>
+<style>
+#serendipityRightSideBar {
+ display: block;
+}
+</style>
+</head>
+
+<body>
+<table><tbody><tr>
+<td>
+You should be able to see a green block at the right of this text<br>
+Closing this page, should not cause a crash.<br>
+
+<script>var x=document.body.offsetHeight;</script>
+</td>
+<td id="serendipityRightSideBar">
+ <iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%3Cbody%20style%3D%22background-color%3Agreen%22%3EYou%20should%20be%20able%20to%20see%20this%20text%3C/body%3E%3C/html%3E"></iframe>
+</td>
+</tr></tbody></table>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/280708-1.html b/layout/base/crashtests/280708-1.html
new file mode 100644
index 0000000000..37ff834282
--- /dev/null
+++ b/layout/base/crashtests/280708-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head>
+<style>
+.rowg {display:table-row-group;}
+</style>
+</head><body onload="document.getElementById('x').className = 'rowg'; document.body.offsetWidth; document.getElementById('y').className = 'rowg'; document.body.offsetWidth; document.documentElement.className = '';">
+<table><tbody><tr>
+<td id="x"><input id="y"></td>
+</tr></tbody></table>
+</body></html>
diff --git a/layout/base/crashtests/280708-2.html b/layout/base/crashtests/280708-2.html
new file mode 100644
index 0000000000..c5a94ab35c
--- /dev/null
+++ b/layout/base/crashtests/280708-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head>
+<style>
+.rowg {display:table-row-group;}
+</style>
+</head><body onload="document.getElementById('x').className = 'rowg'; document.body.offsetWidth; document.getElementById('y').className = 'rowg'; document.body.offsetWidth; document.documentElement.className = '';">
+<table><tbody><tr>
+<td id="y"><input id="x"></td>
+</tr></tbody></table>
+</body></html>
diff --git a/layout/base/crashtests/281333-1.html b/layout/base/crashtests/281333-1.html
new file mode 100644
index 0000000000..20d7ed9af8
--- /dev/null
+++ b/layout/base/crashtests/281333-1.html
@@ -0,0 +1 @@
+<NOFRAMES STYLE="display:table-header-group; clear:inherit;"></NOFRAMES>
diff --git a/layout/base/crashtests/285212-1.html b/layout/base/crashtests/285212-1.html
new file mode 100644
index 0000000000..3452839d9e
--- /dev/null
+++ b/layout/base/crashtests/285212-1.html
@@ -0,0 +1,13 @@
+<BODY STYLE="margin:500px;">
+<DD>
+<OBJECT STYLE="width:500px;">
+<BODY>
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+ 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0 2 4 6 8 0
+<UL>
+</UL>
+</BODY>
+</OBJECT>
+1
+</DD>
+</BODY>
diff --git a/layout/base/crashtests/286813-1.html b/layout/base/crashtests/286813-1.html
new file mode 100644
index 0000000000..05010dc3f2
--- /dev/null
+++ b/layout/base/crashtests/286813-1.html
@@ -0,0 +1,9 @@
+<HTML><HEAD><TITLE>286813</TITLE></HEAD><BODY>
+ <OBJECT>
+ <EMBED>12345678901234567890123456789123456789F<EMBED>
+ <OBJECT>
+ <IFRAME WIDTH="100"> frame </IFRAME>
+ </OBJECT>
+ </OBJECT>
+</BODY></HTML>
+ \ No newline at end of file
diff --git a/layout/base/crashtests/306940-1.html b/layout/base/crashtests/306940-1.html
new file mode 100644
index 0000000000..51cced408f
--- /dev/null
+++ b/layout/base/crashtests/306940-1.html
@@ -0,0 +1,49 @@
+<html>
+<head>
+
+<script>
+
+function init()
+{
+ var c1 = document.getElementById("c1");
+ var f1 = document.getElementById("f1");
+ var a1 = document.getElementById("a1");
+
+ function first()
+ {
+ c1.style.height = "2em";
+ window.status = "A";
+ }
+
+ function second()
+ {
+ c1.style.position = "absolute";
+ c1.style.overflow = "auto";
+ a1.style.position = "absolute";
+ window.status = "B";
+ }
+
+ first();
+ document.documentElement.offsetHeight;
+ second();
+}
+
+</script>
+</head>
+
+<body onload="init();">
+ <div id="c1">
+ <div id="f1">
+ <table>
+ <tr>
+ <td>
+
+ <span id="a1">Foo</span>
+ </td>
+ </tr>
+ </table>
+ </div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/310267-1.xml b/layout/base/crashtests/310267-1.xml
new file mode 100644
index 0000000000..fff0a65558
--- /dev/null
+++ b/layout/base/crashtests/310267-1.xml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="white-space: pre;" class="reftest-wait"><script><![CDATA[
+
+function init() {
+ var docElt = document.documentElement;
+ var firstText = docElt.childNodes[1];
+ var div = docElt.childNodes[2];
+ var bidiText = div.childNodes[0];
+
+ function first()
+ {
+ docElt.insertBefore(div, firstText);
+ docElt.insertBefore(bidiText, div);
+ }
+
+ function second()
+ {
+ docElt.insertBefore(div, firstText);
+ docElt.appendChild(bidiText);
+ document.documentElement.removeAttribute("class");
+ }
+
+ first();
+ setTimeout(second, 100);
+
+}
+
+window.addEventListener("load", init, false);
+
+]]></script>
+
+A<div>׳
+Z</div></html> \ No newline at end of file
diff --git a/layout/base/crashtests/310638-1.svg b/layout/base/crashtests/310638-1.svg
new file mode 100644
index 0000000000..54d5182c82
--- /dev/null
+++ b/layout/base/crashtests/310638-1.svg
@@ -0,0 +1,38 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="reftest-wait"><script><![CDATA[
+
+function init()
+{
+ var div2 = document.getElementById("div2");
+ var div1 = document.getElementById("div1");
+ var docElt = document.documentElement;
+ var titleText = document.createTextNode("foo baz");
+
+ docElt.appendChild(div2);
+ div2.appendChild(titleText);
+
+ function second ()
+ {
+ div2.appendChild(div1);
+ removeNode(titleText);
+ removeNode(div2);
+ document.documentElement.removeAttribute("class");
+ }
+
+ setTimeout(second, 30);
+}
+
+
+function removeNode(q1) { q1.parentNode.removeChild(q1); }
+
+
+setTimeout(init, 30);
+
+
+]]></script>
+
+<div xmlns='http://www.w3.org/1999/xhtml' id="div1">
+
+<div id="div2">bar</div>
+</div>
+
+</svg>
diff --git a/layout/base/crashtests/310638-2.html b/layout/base/crashtests/310638-2.html
new file mode 100644
index 0000000000..34bfc49689
--- /dev/null
+++ b/layout/base/crashtests/310638-2.html
@@ -0,0 +1,19 @@
+<HTML>
+<HEAD>
+
+</HEAD>
+<BODY onload="document.getElementById('s').removeAttribute('style');">
+<span>
+ <span style="display: block;" id="s">This should not crash Mozilla</span>
+</span>
+<div style=" position: absolute;">
+ <span style="position: relative;">
+ <span style="white-space:pre;">
+ <span style="position: absolute;">
+ <span style="float: right;"></span>
+ </span>
+ </span>
+ </span>
+</div>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/311661-1.xhtml b/layout/base/crashtests/311661-1.xhtml
new file mode 100644
index 0000000000..6b49c690ae
--- /dev/null
+++ b/layout/base/crashtests/311661-1.xhtml
@@ -0,0 +1,31 @@
+<?xml version="1.0"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Testcase bug 311661 - Evil xul testcase, using display:table-row causes crash [@ nsTableRowGroupFrame::GetFirstRow]">
+<html:script><![CDATA[
+function doe(i) {
+document.documentElement.getElementsByTagName('*')[i].style.display='table-row';
+document.documentElement.getElementsByTagName('*')[i+1].style.display='table-row';
+i+=1;
+setTimeout(doe2,20,i);
+}
+function doe2(i){
+document.documentElement.getElementsByTagName('*')[i-1].style.display='';
+if (i>1)i=1;
+setTimeout(doe,20,i);
+}
+]]></html:script>
+<button id="button" onclick="doe(1)" label="Mozilla should not crash, when clicking this button"/>
+<script/>
+<html:script>
+function clickbutton()
+{
+ var ev = document.createEvent('MouseEvents');
+ ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ var button = document.getElementById('button');
+ button.dispatchEvent(ev);
+
+ setTimeout(function() { document.documentElement.className = "" }, 500);
+}
+window.addEventListener("load", clickbutton, false);
+</html:script>
+
+</window>
diff --git a/layout/base/crashtests/311661-2.xhtml b/layout/base/crashtests/311661-2.xhtml
new file mode 100644
index 0000000000..4ed2c8f2c8
--- /dev/null
+++ b/layout/base/crashtests/311661-2.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<window xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-wait" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" title="Testcase bug 311661 - Evil xul testcase, using display:table-row causes crash [@ nsTableRowGroupFrame::GetFirstRow]">
+<html:script><![CDATA[
+function doe() {
+document.documentElement.getElementsByTagName('*')[1].style.display='table-row';
+setTimeout(doe2,20);
+}
+function doe2(){
+document.documentElement.getElementsByTagName('*')[1].style.display='';
+setTimeout(doe,20);
+}
+]]></html:script>
+<button id="button" onclick="doe()" label="Mozilla should not crash, when clicking this button"/>
+<div style="display:table-row"/>
+<html:script>
+function clickbutton()
+{
+ var ev = document.createEvent('MouseEvents');
+ ev.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
+ var button = document.getElementById('button');
+ button.dispatchEvent(ev);
+
+ setTimeout(function() { document.documentElement.className = "" }, 500);
+}
+window.addEventListener("load", clickbutton, false);
+</html:script>
+
+</window>
diff --git a/layout/base/crashtests/313086-1.xml b/layout/base/crashtests/313086-1.xml
new file mode 100644
index 0000000000..5ebcf45edb
--- /dev/null
+++ b/layout/base/crashtests/313086-1.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "xhtml1-strict.dtd">
+<html xmlns='http://www.w3.org/1999/xhtml' id="root" class="reftest-wait">
+
+<div id="D1"><div id="D2"/></div>
+
+<script>
+<![CDATA[
+
+function gE(id) { return document.getElementById(id); }
+
+function init()
+{
+ gE("root").style.display = "table";
+
+ gE("D1").style.position = "absolute";
+
+ setTimeout(function() {gE("D2").style.position = "fixed";}, 100);
+ setTimeout(function() {gE("D1").style.overflow = "hidden";}, 200);
+ setTimeout(function() {gE("root").style.width = "200%"; document.documentElement.removeAttribute("class"); }, 300);
+}
+
+window.addEventListener("load", init, false);
+
+]]>
+</script>
+
+</html>
diff --git a/layout/base/crashtests/317285-1.html b/layout/base/crashtests/317285-1.html
new file mode 100644
index 0000000000..bcd84fe06d
--- /dev/null
+++ b/layout/base/crashtests/317285-1.html
@@ -0,0 +1 @@
+<HEAD><BGSOUND STYLE="Ã" STYLE="Ó" LOOP="top" LOOP=í-> LOOP= LOOP=%n%n%n%n%n%n LOOP="E" onLoad="-m" SRCˆ%n%n%n%n%n%n STYLE=÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷÷ onLoad=# SRC=# SRC="-." STYLE=RRRRRRRR><IMG START=&; START="left"><BGSOUND STYLE=-í SRC="‡‡‡" STYLE="&"-$";" LOOP=(( SRC="javascript:"_self""-Ç LOOP=# STYLE=úú LOOP="8888" LOOP="-1""\\\\" SRC="ýýýýýý" SRC="-" SRC="w" LOOP="-¡" LOOP=öööööööö LOOP=çç STYLE=-Æ STYLE=""ææææ"è" STYLEl> \ No newline at end of file
diff --git a/layout/base/crashtests/317934-1-inner.html b/layout/base/crashtests/317934-1-inner.html
new file mode 100644
index 0000000000..d8aeea6bdb
--- /dev/null
+++ b/layout/base/crashtests/317934-1-inner.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+<script>
+function clickit()
+{
+document.getElementById('button').click();
+}
+window.addEventListener('load', clickit);
+</script>
+</head>
+<body>
+<div style="width:400px;">
+<q style="position:relative;"><q style="position:relative;">
+Some random text, some random text, some random text
+<span style="position: relative;">
+Some random text, some random text, some random text
+</span>
+</q></q>
+</div>
+<script>
+function doe(){
+var q1=document.getElementsByTagName('q')[0];
+var q2=document.getElementsByTagName('q')[1];
+q1.style.position='static';
+q2.style.position='static';
+}
+//setTimeout(doe,200);
+</script>
+<button id="button" onclick="doe()">Clicking this button should not crash Mozilla</button>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/317934-1.html b/layout/base/crashtests/317934-1.html
new file mode 100644
index 0000000000..ee77106c57
--- /dev/null
+++ b/layout/base/crashtests/317934-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="317934-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/320459-1.html b/layout/base/crashtests/320459-1.html
new file mode 100644
index 0000000000..2448fa5858
--- /dev/null
+++ b/layout/base/crashtests/320459-1.html
@@ -0,0 +1,7 @@
+ <legend>
+ <kbd>
+ <object>
+ <h4>
+ </object>
+ </kbd>
+
diff --git a/layout/base/crashtests/321058-1.xhtml b/layout/base/crashtests/321058-1.xhtml
new file mode 100644
index 0000000000..1df88d19a2
--- /dev/null
+++ b/layout/base/crashtests/321058-1.xhtml
@@ -0,0 +1,4 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <popupgroup>
+ </popupgroup>
+</window>
diff --git a/layout/base/crashtests/321058-2.xhtml b/layout/base/crashtests/321058-2.xhtml
new file mode 100644
index 0000000000..44b2a3456b
--- /dev/null
+++ b/layout/base/crashtests/321058-2.xhtml
@@ -0,0 +1,25 @@
+<?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" class="reftest-wait" onload="setTimeout(boom, 30);">
+
+<script><![CDATA[
+
+function boom()
+{
+ var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+ var popupgroup = document.createElementNS(XUL_NS, 'popupgroup');
+ document.documentElement.appendChild(popupgroup);
+ document.documentElement.removeChild(popupgroup);
+
+ var tooltip = document.createElementNS(XUL_NS, 'tooltip');
+ document.documentElement.appendChild(tooltip);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]></script>
+
+</window>
diff --git a/layout/base/crashtests/321077-1.xhtml b/layout/base/crashtests/321077-1.xhtml
new file mode 100644
index 0000000000..3cd650eac1
--- /dev/null
+++ b/layout/base/crashtests/321077-1.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <tree>
+ <treechildren/>
+ </tree>
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/321077-2.xhtml b/layout/base/crashtests/321077-2.xhtml
new file mode 100644
index 0000000000..9b0616843f
--- /dev/null
+++ b/layout/base/crashtests/321077-2.xhtml
@@ -0,0 +1,22 @@
+<?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" onload="setTimeout(boom, 0);">
+
+<script type="text/javascript">
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function boom()
+{
+ // Fire off an image load, then leave while the image load is pending.
+
+ document.getElementById("image").src = "data:text/html,foo";
+ location = "data:text/html,elsewhere";
+}
+
+</script>
+
+<tree><treechildren/></tree><image id="image"/>
+
+</window>
diff --git a/layout/base/crashtests/322436-1.html b/layout/base/crashtests/322436-1.html
new file mode 100644
index 0000000000..907ddddc15
--- /dev/null
+++ b/layout/base/crashtests/322436-1.html
@@ -0,0 +1,31 @@
+<html class="reftest-wait">
+
+<head>
+
+
+
+<script>
+
+function foo()
+{
+ setTimeout(bar, 30);
+}
+
+function bar()
+{
+ document.getElementById("TT").style.position = "absolute";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="foo();">
+
+
+<div id="TT"><div style="position: fixed;"><div style="display: -moz-box;"><div style="float: left;"></div></div></div></div>
+
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/322678.html b/layout/base/crashtests/322678.html
new file mode 100644
index 0000000000..dffa35d006
--- /dev/null
+++ b/layout/base/crashtests/322678.html
@@ -0,0 +1,27 @@
+<!-- Quirks mode on purpose -->
+<html>
+ <head>
+ <title>Testcase bug 322678 - Crash [@ nsIFrame::GetParent] with evil testcase position:relative/absolute/display:table-column, etc</title>
+ <script>
+ function run(){
+ document.body.offsetHeight;
+ document.getElementById('one').removeAttribute('style');
+ document.body.offsetHeight;
+ document.getElementById('two').removeAttribute('style');
+ document.body.offsetHeight;
+ }
+ </script>
+ </head>
+ <body onload="run();">
+ <span>
+ <div style="position: relative;">
+ <span style="position: absolute;"></span>
+ </div>
+
+ <span id="one" style="display: table-column;">
+ <span id="two" style="display: block; position: relative;">
+ </span>
+ </span><u style="display: table-cell;"> </u>
+ </span>
+ </body>
+</html>
diff --git a/layout/base/crashtests/325024.html b/layout/base/crashtests/325024.html
new file mode 100644
index 0000000000..6f9aba509e
--- /dev/null
+++ b/layout/base/crashtests/325024.html
@@ -0,0 +1,20 @@
+<html><head>
+<title>Testcase bug 325024 - Crash with evil testcase, using object, display: table-column, etc</title>
+</head>
+<body>
+<object>
+<div>Mozilla should not crash on this page</div>
+<span style="display: table-column;">
+<span style="display: block;"></span>
+</span><span style="display: table-cell;">
+</span>
+<isindex style="position: absolute; ">
+</object>
+
+<script>
+document.body.getElementsByTagName('*')[2].removeAttribute('style');
+document.body.offsetHeight;
+document.body.getElementsByTagName('*')[3].removeAttribute('style');
+document.body.offsetHeight;
+</script>
+</body></html>
diff --git a/layout/base/crashtests/325218.xhtml b/layout/base/crashtests/325218.xhtml
new file mode 100644
index 0000000000..321b201088
--- /dev/null
+++ b/layout/base/crashtests/325218.xhtml
@@ -0,0 +1,25 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ class="reftest-wait"
+ title="Testcase bug 325218 - Crash with evil xul testcase, using box, tooltip, object, etc">
+<html:span>
+ <box>
+ <tooltip/>
+ </box>
+
+ <html:td/><html:object style="display: none;">This should not crash Mozilla
+ <html:span style="display: table;"/>
+ </html:object>
+</html:span>
+
+<html:script>
+function doe(){
+ document.getElementsByTagName('html:object')[0].removeAttribute('style');
+ document.getElementsByTagName('html:object')[0].offsetHeight;
+ document.getElementsByTagName('html:span')[1].removeAttribute('style');
+ document.getElementsByTagName('html:object')[0].setAttribute('style', 'text-decoration: underline');
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(doe,50);
+</html:script>
+</window>
diff --git a/layout/base/crashtests/325967-1.html b/layout/base/crashtests/325967-1.html
new file mode 100644
index 0000000000..9231d7ff3f
--- /dev/null
+++ b/layout/base/crashtests/325967-1.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+
+<script>
+
+function init()
+{
+ var ww = document.getElementById("ww");
+ var inp = document.getElementById("inp");
+
+ document.addEventListener("DOMNodeInserted", u);
+
+ document.body.appendChild(ww);
+
+ function u()
+ {
+ document.removeEventListener("DOMNodeInserted", u);
+ ww.removeChild(inp);
+ document.documentElement.removeAttribute("class");
+ }
+}
+
+</script>
+
+</head>
+
+<body onload="init()"><div id="ww"><input type="text" value="inputtext" id="inp">moretext</div></body>
+
+</html>
diff --git a/layout/base/crashtests/325984-1.xhtml b/layout/base/crashtests/325984-1.xhtml
new file mode 100644
index 0000000000..eee6acff9b
--- /dev/null
+++ b/layout/base/crashtests/325984-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<body><table><col onload="3"/>foo</table></body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/325984-2.html b/layout/base/crashtests/325984-2.html
new file mode 100644
index 0000000000..b17ef4197e
--- /dev/null
+++ b/layout/base/crashtests/325984-2.html
@@ -0,0 +1,31 @@
+<html>
+ <head>
+ <title>colgroup pseudos</title>
+ <style>
+ div.table {background-color:red; color:yellow; display:table}
+ div.col {background-color:green; width:400px; display:table-column}
+
+ </style>
+ </head>
+ <body>
+ <div class="table">
+ <div class="col" ></div> anonymous content
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-cell">anonymous cell</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-row">anonymous row</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-row-group">anonymous rowgroup</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table">anonymous table</div>
+ </div>
+<div class="table">
+ <div class="col" ></div> <div style="display:table-caption">anonymous caption</div>
+ </div>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/328944-1.xhtml b/layout/base/crashtests/328944-1.xhtml
new file mode 100644
index 0000000000..ae7c824bc2
--- /dev/null
+++ b/layout/base/crashtests/328944-1.xhtml
@@ -0,0 +1,23 @@
+<?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">
+
+<script>
+
+function gE(i) { return document.getElementById(i); }
+
+function init()
+{
+ gE("button").insertBefore(gE("popup"), gE("hbox4"));
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+<menupopup id="popup"/>
+
+<button id="button"><hbox/><hbox/><hbox/><hbox id="hbox4"/></button>
+
+</window>
diff --git a/layout/base/crashtests/329900-1.html b/layout/base/crashtests/329900-1.html
new file mode 100644
index 0000000000..54d7021491
--- /dev/null
+++ b/layout/base/crashtests/329900-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>Testcase bug 329900 - Crash with evil testcase, using table-column-group, table-column, table-cell</title>
+</head>
+<body>
+Mozilla should not crash with this page
+<div style="display: table-cell;">
+ <span style="display: table-cell;"></span>
+ <span style="display: table-column;"></span>
+ <span style="display: table-column-group;"></span>
+ <span style="display: table-cell;"></span>
+ <table></table>
+</div>
+
+</body></html>
diff --git a/layout/base/crashtests/330015-1.html b/layout/base/crashtests/330015-1.html
new file mode 100644
index 0000000000..84e66edc58
--- /dev/null
+++ b/layout/base/crashtests/330015-1.html
@@ -0,0 +1,14 @@
+<html><head style="display: table-row;">
+<title>Testcase bug 330015 - Crash with display: table-column-group, table-row, table-column, etc</title>
+<link style="display: table-row;">
+<link style="display: block;">
+<link style="display: table-column;">
+<link style="display: table-column-group;">
+</head>
+<body>
+Mozilla should not crash on this page.
+<script>
+document.getElementsByTagName('head')[0].style.display = '';
+document.getElementsByTagName('link')[1].style.display = '';
+</script>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/331679-1.xhtml b/layout/base/crashtests/331679-1.xhtml
new file mode 100644
index 0000000000..2989491973
--- /dev/null
+++ b/layout/base/crashtests/331679-1.xhtml
@@ -0,0 +1,36 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<title>Bug 331679 testcase</title>
+
+
+<style id="style">
+.cat::-moz-table-row-group { overflow: scroll; }
+.toad { position: absolute; }
+</style>
+
+<script>
+
+function init()
+{
+ document.getElementById("style").textContent += "table::-moz-table-row { opacity: 0.2; }";
+ document.getElementById("row").setAttribute("class", "toad");
+ document.getElementById("table").setAttribute("class", "cat");
+}
+
+window.addEventListener("load", init, false);
+
+</script>
+
+</head>
+
+<body>
+
+<table id="table">
+ <tr id="row">
+ <td>Cell</td>
+ </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/331679-2.xml b/layout/base/crashtests/331679-2.xml
new file mode 100644
index 0000000000..7f4e8184a4
--- /dev/null
+++ b/layout/base/crashtests/331679-2.xml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>pseudo double SetInitialChildlist</title>
+ <style>
+ .cat::-moz-table-row-group { overflow: scroll;}
+ tr { position: absolute;}
+ </style>
+ </head>
+
+ <body>
+
+ <table class="cat">
+ <tr>
+ <td>Cell</td>
+ </tr>
+ </table>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/331679-3.xml b/layout/base/crashtests/331679-3.xml
new file mode 100644
index 0000000000..df73640be6
--- /dev/null
+++ b/layout/base/crashtests/331679-3.xml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>pseudo double SetInitialChildlist</title>
+ <style>
+ .cat::-moz-table-row-group { overflow: scroll;}
+ tr { position: absolute;}
+ </style>
+ </head>
+
+ <body>
+
+ <div class="cat" style="display:table">
+ <div style="display:block">
+ <div style="display:table-cell">Cell</div>
+ </div>
+ </div>
+
+ </body>
+</html>
diff --git a/layout/base/crashtests/331883-1-inner.html b/layout/base/crashtests/331883-1-inner.html
new file mode 100644
index 0000000000..0df3103937
--- /dev/null
+++ b/layout/base/crashtests/331883-1-inner.html
@@ -0,0 +1,30 @@
+<html>
+
+<head style="display: none">
+
+<style id="style">
+.lizard:first-line { }
+</style>
+
+<script>
+
+function init()
+{
+ document.getElementById("style").textContent += "* { position: relative; }";
+ document.getElementById("comment10div").setAttribute("class", "lizard");
+ document.getElementById("style").textContent += "*::-moz-line-frame { position: absolute; }";
+ setTimeout(function() { location.reload(); }, 200);
+}
+
+window.addEventListener("load", init);
+
+</script>
+
+</head>
+
+<body>
+
+<div id="comment10div">XXXXXXXXXXXXXXXXXXXXXXXX <span>YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ JJJJJJJJJJJJJJJ PPPPPPPPPPPPPPPPPPPPPPPPPPPPPPPP</span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/331883-1.html b/layout/base/crashtests/331883-1.html
new file mode 100644
index 0000000000..b0c2339dd0
--- /dev/null
+++ b/layout/base/crashtests/331883-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait">
+<head>
+<script>
+var numLoads = 0;
+function loaded()
+{
+ numLoads++;
+ if (numLoads == 5) {
+ document.documentElement.className = "";
+ }
+}
+</script>
+<body>
+<iframe onload="loaded()" src="331883-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/335140-1.html b/layout/base/crashtests/335140-1.html
new file mode 100644
index 0000000000..9ed0b8bd42
--- /dev/null
+++ b/layout/base/crashtests/335140-1.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+
+<body>
+
+<span style="position: relative;">
+ <br> <span style="position: absolute;">Login</span>
+</span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/336291-1.html b/layout/base/crashtests/336291-1.html
new file mode 100644
index 0000000000..cbcb6c0c9a
--- /dev/null
+++ b/layout/base/crashtests/336291-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script>
+function z()
+{
+ document.getElementById("x").style.display = "table";
+ document.body.style.display = "table-row";
+}
+</script>
+</head>
+
+<body onload="z()">
+
+<p style="display: table-row"></p>
+
+<p id="x"></p>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/336999-1.xhtml b/layout/base/crashtests/336999-1.xhtml
new file mode 100644
index 0000000000..193b681613
--- /dev/null
+++ b/layout/base/crashtests/336999-1.xhtml
@@ -0,0 +1,24 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+<script>
+
+function boom()
+{
+ document.getElementById("xxx").style.position = "fixed";
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function(){setTimeout(boom, 30)}, 0);
+
+</script>
+
+ <hbox id="xxx" style="position: absolute; display: block;">
+ <label value="X" />
+ <menulist>
+ <menupopup>
+ <menuitem label="Y" />
+ </menupopup>
+ </menulist>
+ </hbox>
+
+</window>
diff --git a/layout/base/crashtests/337066-1.xhtml b/layout/base/crashtests/337066-1.xhtml
new file mode 100644
index 0000000000..fadc453f40
--- /dev/null
+++ b/layout/base/crashtests/337066-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function init()
+{
+ var A = document.getElementById("A");
+ var B = document.getElementById("B");
+
+ for (var i = 0; i &lt; 2; ++i)
+ B.insertBefore(document.createElement("span"), A);
+}
+
+</script>
+</head>
+
+<body onload="init()">
+
+<em id="B"><td></td><span id="A"><div></div></span></em>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/337268-1.html b/layout/base/crashtests/337268-1.html
new file mode 100644
index 0000000000..8acd303e69
--- /dev/null
+++ b/layout/base/crashtests/337268-1.html
@@ -0,0 +1,45 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+window.addEventListener("load", foo1);
+
+function foo1()
+{
+ document.getElementById("a").style.width = "20em";
+ setTimeout(foo2, 30);
+}
+
+function foo2()
+{
+ document.getElementById("b").style.width = "auto";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<table>
+<tr>
+<td id="a">
+
+<table style="display: -moz-inline-box;">
+<tr>
+<td width="100%">
+
+XXX XXX
+
+<div id="b" style="width: 200%; display: table-column-group;"></div>
+
+</td>
+</tr>
+</table>
+
+</td>
+</tr>
+</table>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/337419-1.html b/layout/base/crashtests/337419-1.html
new file mode 100644
index 0000000000..e4eabe1559
--- /dev/null
+++ b/layout/base/crashtests/337419-1.html
@@ -0,0 +1,23 @@
+<html>
+<head>
+
+<style>
+#container {
+ column-count: 3;
+}
+#right {
+ float: right;
+ overflow: hidden;
+}
+</style>
+
+<link rel="alternate" type="application/atom+xml" title="Atom" href="http://weblogs.mozillazine.org/roc/atom.xml" />
+
+</head>
+
+<body>
+
+<div id="container">X<div id="right"></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/337476-1.xhtml b/layout/base/crashtests/337476-1.xhtml
new file mode 100644
index 0000000000..b04752fc7a
--- /dev/null
+++ b/layout/base/crashtests/337476-1.xhtml
@@ -0,0 +1,32 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="reftest-wait">
+
+
+<script>
+
+<![CDATA[
+
+window.addEventListener("load", init, false);
+
+function init()
+{
+ document.getElementById("n1").style.display = "table-caption";
+ setTimeout(init2, 30);
+}
+
+function init2()
+{
+ document.getElementById("n2").style.display = "table-caption";
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+
+</script>
+
+
+ <hbox>
+ <vbox flex="1" id="n1"/>
+ <spacer flex="1" id="n2"/>
+ </hbox>
+
+</window>
diff --git a/layout/base/crashtests/338703-1.html b/layout/base/crashtests/338703-1.html
new file mode 100644
index 0000000000..54591fc169
--- /dev/null
+++ b/layout/base/crashtests/338703-1.html
@@ -0,0 +1,29 @@
+<html>
+
+<head>
+
+<style id="style"></style>
+<script>
+
+function hmm()
+{
+ document.getElementById("style").textContent = "td { overflow: scroll; } table { background: lightblue; }";
+}
+
+
+</script>
+
+
+
+</head>
+
+
+<body onload="hmm()">
+
+
+
+<table><tr><td>Foopy</td></tr></table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/339651-1.html b/layout/base/crashtests/339651-1.html
new file mode 100644
index 0000000000..c7860c3882
--- /dev/null
+++ b/layout/base/crashtests/339651-1.html
@@ -0,0 +1,37 @@
+<html style="border: 1px solid red; width: 6em;" class="reftest-wait">
+
+<head>
+<script type="text/javascript">
+
+function f1()
+{
+ document.getElementById("s").style.cssFloat = "left";
+
+ document.body.style.display = "inline";
+ document.getElementById("d").style.display = "inline";
+ document.getElementById("p").style.display = "inline";
+
+ setTimeout(f2, 30);
+}
+
+function f2()
+{
+ document.getElementById("d").style.cssFloat = "left";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="f1()">
+
+TTTTT TTTTT
+<div id="d">
+YY
+<p id="p">
+ZZ
+<span id="s">
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/340093-1.xhtml b/layout/base/crashtests/340093-1.xhtml
new file mode 100644
index 0000000000..229ca21820
--- /dev/null
+++ b/layout/base/crashtests/340093-1.xhtml
@@ -0,0 +1,11 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml">
+
+ <html:style>
+ menulist, menulist * {
+ overflow: scroll;
+ }
+ </html:style>
+
+ <menulist/>
+
+</window>
diff --git a/layout/base/crashtests/341382-1.html b/layout/base/crashtests/341382-1.html
new file mode 100644
index 0000000000..a42e8690f5
--- /dev/null
+++ b/layout/base/crashtests/341382-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait"><head>
+<title>Testcase bug 341382 - Crash [@ DoDeletingFrameSubtree] with position:fixed and display: table-caption</title>
+<script>
+function removestyles(i){
+document.getElementById('one').removeAttribute('style');
+document.body.offsetHeight;
+document.getElementById('two').removeAttribute('style');
+document.documentElement.removeAttribute("class");
+}
+
+
+</script></head>
+<body onload="setTimeout(removestyles,0);">
+<span></span>
+<table style="display: table-row-group;">
+<table>
+<span id="one" style="display: table-caption;">
+ <span style="position: fixed;"></span>
+ <div id="two" style="display: table-caption;"></div>
+</span>
+</body>
+</html>
diff --git a/layout/base/crashtests/341382-2.html b/layout/base/crashtests/341382-2.html
new file mode 100644
index 0000000000..13216fac45
--- /dev/null
+++ b/layout/base/crashtests/341382-2.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait"><head><script>
+function removestyles(i){
+document.getElementById('one').removeAttribute('style');
+document.body.offsetHeight;
+document.getElementById('two').removeAttribute('style');
+document.documentElement.removeAttribute("class");
+}
+
+</script></head><body onload="setTimeout(removestyles,0);"><table style="display: table-row-group;"><table><span id="one" style="display: table-caption;"><i style="position: fixed;"></i><div id="two" style="display: table-caption;"></div></span></body></html>
diff --git a/layout/base/crashtests/341858-1.html b/layout/base/crashtests/341858-1.html
new file mode 100644
index 0000000000..97c9698e45
--- /dev/null
+++ b/layout/base/crashtests/341858-1.html
@@ -0,0 +1,14 @@
+
+<table style="display: table-caption;">
+<keygen style="display: table-caption;">
+<span style="display: table-caption;">
+<span style="display: table-row-group;">
+
+<body style="display: table-row-group;">
+<input>
+
+
+
+
+
+
diff --git a/layout/base/crashtests/342145-1.xhtml b/layout/base/crashtests/342145-1.xhtml
new file mode 100644
index 0000000000..8d87cb8186
--- /dev/null
+++ b/layout/base/crashtests/342145-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var img = document.getElementById("img");
+ var t1 = img.childNodes[1]; // a whitespace text node
+ var t2 = document.createTextNode(' ');
+
+ img.insertBefore(t2, t1);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<map name="map" id="map"><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#map" id="img"><area href="http://www.mozilla.org/" shape="rect" coords="0,0,100,100" id="hhh" /> </img></map>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/343293-1.xhtml b/layout/base/crashtests/343293-1.xhtml
new file mode 100644
index 0000000000..84da4e1b42
--- /dev/null
+++ b/layout/base/crashtests/343293-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<title>Testcase bug 343293 - Crash [@ nsLayoutUtils::GetFloatFromPlaceholder] using ::first-line, floats, caption and generated content</title>
+<style>
+*::first-line { }
+*::before { content:"--"; }
+</style>
+<script>
+function doe() {
+document.getElementsByTagName('caption')[0].removeAttribute('style');
+document.documentElement.offsetHeight;
+document.getElementsByTagName('span')[0].removeAttribute('style');
+}
+window.onload=doe;
+</script>
+
+<caption style="float: left;"></caption>
+<span style="float: right;"></span>
+This should not crash Mozilla
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/343293-2.xhtml b/layout/base/crashtests/343293-2.xhtml
new file mode 100644
index 0000000000..18be6c9aec
--- /dev/null
+++ b/layout/base/crashtests/343293-2.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<style>
+html::first-line { }
+</style>
+<script>
+function doe() {
+ document.getElementsByTagName('caption')[0].removeAttribute('style');
+}
+window.onload=doe;
+</script>
+
+<caption style="float: left;"></caption>
+<span style="float: right;"></span>
+</html>
diff --git a/layout/base/crashtests/343540-1.html b/layout/base/crashtests/343540-1.html
new file mode 100644
index 0000000000..43291c6d28
--- /dev/null
+++ b/layout/base/crashtests/343540-1.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+
+<script>
+
+function boo()
+{
+ var div = document.getElementById("div");
+ var dd = document.getElementById("dd");
+ var newSpan = document.createElement('span');
+ dd.insertBefore(newSpan, div);
+}
+
+window.addEventListener("load", boo);
+</script>
+
+</head>
+
+
+<body>
+
+<dd id="dd"><div id="div"></div></dd>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/344057-1.xhtml b/layout/base/crashtests/344057-1.xhtml
new file mode 100644
index 0000000000..74241de465
--- /dev/null
+++ b/layout/base/crashtests/344057-1.xhtml
@@ -0,0 +1,9 @@
+<command xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: table-row;">
+<small xmlns="http://www.w3.org/1999/xhtml" style="float: right;">Ë <semantics xmlns="http://www.w3.org/1998/Math/MathML" style="float: right;">Ë <colgroup xmlns="http://www.w3.org/1999/xhtml" style="float: left;">Ë <s style="display: table-row;">
+<u style="display: table-row;"/>
+<p style="display: table;"/>
+</s>
+</colgroup>
+</semantics>
+</small>
+</command> \ No newline at end of file
diff --git a/layout/base/crashtests/344064-1-inner.xhtml b/layout/base/crashtests/344064-1-inner.xhtml
new file mode 100644
index 0000000000..b47dd1ae74
--- /dev/null
+++ b/layout/base/crashtests/344064-1-inner.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" >
+<script>
+
+function removestyles(){
+ var x=document.getElementById('x');
+ x.removeAttribute('style');
+}
+
+setTimeout(removestyles,400);
+
+</script>
+<div><xul:editor id="x" style="display: block; float: left;"></xul:editor></div></html>
diff --git a/layout/base/crashtests/344064-1.html b/layout/base/crashtests/344064-1.html
new file mode 100644
index 0000000000..c80e1341ed
--- /dev/null
+++ b/layout/base/crashtests/344064-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="344064-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/344300-1-inner.xhtml b/layout/base/crashtests/344300-1-inner.xhtml
new file mode 100644
index 0000000000..3e980e8c7b
--- /dev/null
+++ b/layout/base/crashtests/344300-1-inner.xhtml
@@ -0,0 +1,36 @@
+<hx xmlns="http://www.w3.org/1999/xhtml" style="display: table;">
+<script>
+/*template*/
+var doc = document;
+if (document.getElementById('content'))
+ doc = document.getElementById('content').contentDocument;
+
+function addstyles(){
+var x=doc.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+*::first-line { text-transform: uppercase; background-color:green; font-size:110%;}\
+*::after { content:"anonymous text"; float:right;border:3px solid black;text-transform: uppercase;}\
+*::before { content:"before text"; float:right;border:3px solid black;font-size: 10px;}\
+*::-moz-selection { outline: 2px solid blue;}\
+';
+doc.documentElement.appendChild(x);
+}
+
+function removestyles(i){
+
+
+var x=doc.getElementsByTagName('*');
+
+if (x[i])
+ {
+x[i].removeAttribute('style');
+}
+else { i = 0; }
+ i++;
+setTimeout(removestyles,50,i);
+}
+setTimeout(addstyles,200);
+setTimeout(removestyles,500,0);
+/*template*/
+</script>
+<var style="display: table-column-group;" onmouseover="this.removeAttribute('style')">ý <q style="display: table-footer-group;" onmouseover="this.removeAttribute('style')">ý </q><ins style="display: table-cell;" onmouseover="this.removeAttribute('style')">ý <p style="display: list-item;" onmouseover="this.removeAttribute('style')">ý </p><object style="display: -moz-inline-box;" onmouseover="this.removeAttribute('style')">ý </object></ins></var><table style="display: -moz-inline-box;" onmouseover="this.removeAttribute('style')">ý <ins style="display: -moz-inline-block;" onmouseover="this.removeAttribute('style')">ý </ins></table><body style="display: table-column-group;" onmouseover="this.removeAttribute('style')">ý </body><q style="display: table;" onmouseover="this.removeAttribute('style')">ý </q></hx> \ No newline at end of file
diff --git a/layout/base/crashtests/344300-1.html b/layout/base/crashtests/344300-1.html
new file mode 100644
index 0000000000..1c5cb43211
--- /dev/null
+++ b/layout/base/crashtests/344300-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="344300-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/344340-1.xhtml b/layout/base/crashtests/344340-1.xhtml
new file mode 100644
index 0000000000..8a54f759ac
--- /dev/null
+++ b/layout/base/crashtests/344340-1.xhtml
@@ -0,0 +1,28 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="setTimeout(foopy, 30);" class="reftest-wait">
+
+<script>
+
+<![CDATA[
+
+var XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+
+function foopy()
+{
+ var hbox = document.createElementNS(XUL_NS, 'hbox');
+ var tooltip = document.createElementNS(XUL_NS, 'tooltip');
+ var vbox = document.createElementNS(XUL_NS, 'vbox');
+ var toolbarspring = document.createElementNS(XUL_NS, 'toolbarspring');
+
+ document.documentElement.appendChild(hbox);
+ hbox.appendChild(toolbarspring);
+
+ vbox.appendChild(tooltip);
+ toolbarspring.appendChild(vbox);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+
+</window>
diff --git a/layout/base/crashtests/347898-1.html b/layout/base/crashtests/347898-1.html
new file mode 100644
index 0000000000..d66b5b2e72
--- /dev/null
+++ b/layout/base/crashtests/347898-1.html
@@ -0,0 +1,9 @@
+<html>
+<body>
+<table>
+<ul style="display: table-caption;">
+<keygen style="display: table-caption;">
+
+</td>
+</body>
+</html>
diff --git a/layout/base/crashtests/348126-1-inner.html b/layout/base/crashtests/348126-1-inner.html
new file mode 100644
index 0000000000..aafb6c7895
--- /dev/null
+++ b/layout/base/crashtests/348126-1-inner.html
@@ -0,0 +1,28 @@
+<html><head><title>Testcase bug 348126 - Crash [@ nsImageFrame::SourceRectToDest] on reload and removing table-caption styles</title>
+
+<script>
+function removestyles(i){
+
+document.getElementsByTagName('table')[0].removeAttribute('style');
+
+document.getElementsByTagName('object')[0].removeAttribute('style');
+
+document.getElementsByTagName('table')[1].removeAttribute('style');
+document.location.reload();
+}
+
+setTimeout(removestyles,500,0);
+</script>
+</head><body>
+Mozilla should not crash on reload on this page<br>
+<object><table style="display: table-caption;">
+<tbody><tr><td></td></tr></tbody>
+</table><object style="display: table-caption;">
+</object><table style="display: table-row-group;">
+<tbody><tr><td>
+<img src="348126-1.gif">
+</td></tr></tbody></table>
+<img src="348126-1.gif">
+</object>
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/348126-1.gif b/layout/base/crashtests/348126-1.gif
new file mode 100644
index 0000000000..475ea8c164
--- /dev/null
+++ b/layout/base/crashtests/348126-1.gif
Binary files differ
diff --git a/layout/base/crashtests/348126-1.html b/layout/base/crashtests/348126-1.html
new file mode 100644
index 0000000000..2ac1e0da82
--- /dev/null
+++ b/layout/base/crashtests/348126-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="348126-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/348688-1.html b/layout/base/crashtests/348688-1.html
new file mode 100644
index 0000000000..84bcd9fd40
--- /dev/null
+++ b/layout/base/crashtests/348688-1.html
@@ -0,0 +1,24 @@
+<!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 #1 for bug 348688</title>
+<script>
+function boom() {
+ var e = document.getElementById('inline1');
+ e.remove();
+
+ e = document.getElementById('inline2');
+ e.remove();
+ var x = document.body.offsetHeight;
+}
+</script>
+</head>
+<body onload="boom()">
+
+<div style="overflow:hidden">
+<font><span id="inline1"><b id="float1" style="float:left">x</b></span></font>
+<i id="inline2">y</i>
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/348708-1.xhtml b/layout/base/crashtests/348708-1.xhtml
new file mode 100644
index 0000000000..c28cfe7786
--- /dev/null
+++ b/layout/base/crashtests/348708-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script>
+function foopy()
+{
+ var optgroup = document.getElementById("optgroup");
+ var newspan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ optgroup.insertBefore(newspan, optgroup.firstChild);
+}
+</script>
+</head>
+
+<body onload="foopy()">
+
+<select><optgroup label="optgroup" id="optgroup"><option>option</option></optgroup></select>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/348729-1-inner.html b/layout/base/crashtests/348729-1-inner.html
new file mode 100644
index 0000000000..38f8d615a0
--- /dev/null
+++ b/layout/base/crashtests/348729-1-inner.html
@@ -0,0 +1,29 @@
+<html><head>
+<title>Testcase bug - Crash [@ nsRuleNode::GetParentData]</title>
+<script>
+function addstyles1(){
+var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+*::first-letter {float: right; }\
+';
+document.documentElement.appendChild(x);
+
+setTimeout(removestyles,500);
+}
+setTimeout(addstyles1,200);
+
+function removestyles(i){
+document.getElementsByTagName('tfoot')[0].removeAttribute('style');
+document.getElementsByTagName('table')[0].removeAttribute('style');
+
+window.parent.document.documentElement.className = "";
+}
+</script>
+<style>
+*::before { content:"before text";}
+</style>
+</head><body>
+<table style="display: block;">
+<tbody><tr><td></td></tr></tbody><tfoot style="position: absolute;"></tfoot>
+</table>
+</body></html>
diff --git a/layout/base/crashtests/348729-1.html b/layout/base/crashtests/348729-1.html
new file mode 100644
index 0000000000..af577b3a0a
--- /dev/null
+++ b/layout/base/crashtests/348729-1.html
@@ -0,0 +1,6 @@
+<html class="reftest-wait">
+<head>
+<body>
+<iframe src="348729-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/349095-1.xhtml b/layout/base/crashtests/349095-1.xhtml
new file mode 100644
index 0000000000..6d3448376b
--- /dev/null
+++ b/layout/base/crashtests/349095-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+body:first-letter { }
+body { display: inline; }
+</style>
+
+<script>
+function foo()
+{
+ document.getElementById("aa").style.display = "block";
+}
+</script>
+
+</head>
+
+<body onload="foo()">
+ <input type="text" style="display: block;" />
+ <span>Z</span>
+ <span id="aa">A</span>
+ <span style="display: block;">B</span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/350267-1.html b/layout/base/crashtests/350267-1.html
new file mode 100644
index 0000000000..f6e5f86696
--- /dev/null
+++ b/layout/base/crashtests/350267-1.html
@@ -0,0 +1,2 @@
+<samp style="display: -moz-inline-block;">
+<object style="display: block;"> \ No newline at end of file
diff --git a/layout/base/crashtests/354133-1-inner.xhtml b/layout/base/crashtests/354133-1-inner.xhtml
new file mode 100644
index 0000000000..8003a3e991
--- /dev/null
+++ b/layout/base/crashtests/354133-1-inner.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" xmlns:mathml="http://www.w3.org/1998/Math/MathML" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<title>Testcase bug 354133 - Crash [@ nsBlockBandData::Init] with unminimised stirdom mathml/xul testcase</title>
+</head>
+<body>
+This page should not crash Mozilla
+<xul:scrollbar>
+<mathml:ms id="a">
+<mathml:sinh>
+<xul:box id="b"/>
+</mathml:sinh>
+</mathml:ms>
+</xul:scrollbar>
+
+<html:script>
+function stirdom(){
+document.getElementById('a').appendChild(document.getElementById('b'));
+}
+setTimeout(stirdom,200);
+</html:script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/354133-1.html b/layout/base/crashtests/354133-1.html
new file mode 100644
index 0000000000..acb6e4a831
--- /dev/null
+++ b/layout/base/crashtests/354133-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 1000);
+</script>
+<body>
+<iframe src="354133-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/354766-1.xhtml b/layout/base/crashtests/354766-1.xhtml
new file mode 100644
index 0000000000..bb491036ec
--- /dev/null
+++ b/layout/base/crashtests/354766-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+</head>
+
+<body>
+
+<table style="border-collapse: collapse;">
+ <tbody>
+ <tr>
+ <td><mtd xmlns="http://www.w3.org/1998/Math/MathML"/></td>
+ </tr>
+ </tbody>
+</table>
+
+</body>
+
+</html>
+
diff --git a/layout/base/crashtests/355989-1.xhtml b/layout/base/crashtests/355989-1.xhtml
new file mode 100644
index 0000000000..1af1c3273c
--- /dev/null
+++ b/layout/base/crashtests/355989-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+ body, #tq { display: inline; }
+ #tq { position: relative; }
+</style>
+
+<style id="newstyle">
+</style>
+
+<script>
+function foo()
+{
+ document.getElementById("tq").style.position = "static";
+ document.getElementById("newstyle").textContent = "*:first-letter { }";
+}
+</script>
+
+</head>
+
+<body onload="foo()">
+ <table><tr><td>Table</td></tr></table>
+ <div id="tq">Div</div>
+</body>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/355993-1.xhtml b/layout/base/crashtests/355993-1.xhtml
new file mode 100644
index 0000000000..e902ee550e
--- /dev/null
+++ b/layout/base/crashtests/355993-1.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<style>
+body, body * { position: fixed; }
+</style>
+</head>
+
+<body>
+
+
+<div>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+
+ <mtable>
+ <mtr>
+ <mtd>
+ <mn>1</mn>
+ </mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/356325-1.xhtml b/layout/base/crashtests/356325-1.xhtml
new file mode 100644
index 0000000000..c139e8f058
--- /dev/null
+++ b/layout/base/crashtests/356325-1.xhtml
@@ -0,0 +1,20 @@
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<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"
+title="Testcase bug 356325 - Crash [@ nsCSSFrameConstructor::FindFrameWithContent] with tooltip, mathml:and and moving stuff in it">
+<description value="This page should not crash Mozilla"/>
+<box id="y">
+ <box id="d"/>
+</box>
+<tooltip>
+ <mathml:and id="x"/>
+</tooltip>
+
+<html:script>
+function doe() {
+document.getElementById('x').appendChild(document.getElementById('y'));
+document.getElementById('y').appendChild(document.getElementById('d'));
+}
+window.onload=doe;
+</html:script>
+
+</window> \ No newline at end of file
diff --git a/layout/base/crashtests/358729-1.xhtml b/layout/base/crashtests/358729-1.xhtml
new file mode 100644
index 0000000000..b9a3cc35fb
--- /dev/null
+++ b/layout/base/crashtests/358729-1.xhtml
@@ -0,0 +1,52 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<head>
+<script>
+<![CDATA[
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+function foo()
+{
+ var DIVa = document.getElementById('a');
+
+ var DIVb = document.createElementNS(HTML_NS, 'div');
+ DIVb.appendChild(document.createTextNode('DIVb'));
+ DIVa.appendChild(DIVb);
+
+ document.body.offsetHeight;
+
+ var DIVc = document.createElementNS(HTML_NS, 'div');
+ DIVc.appendChild(document.createTextNode('DIVc'));
+ DIVb.appendChild(DIVc);
+
+ document.documentElement.removeAttribute("class");
+}
+
+]]>
+</script>
+</head>
+
+<body onload="setTimeout(foo, 30)">
+
+<div>
+
+<table border="1">
+ <tr>
+ <td>
+ <span dir="ltr">
+ span
+ <th>
+ <div id="a"></div>
+ </th>
+ </span>
+ </td>
+ </tr>
+</table>
+
+<div><span dir="rtl">RTL</span></div>
+</div>
+
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/360339-1.xhtml b/layout/base/crashtests/360339-1.xhtml
new file mode 100644
index 0000000000..f9c20f960d
--- /dev/null
+++ b/layout/base/crashtests/360339-1.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css" ?>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<html:style>
+* { float: right; }
+*:not(style) {
+ /* At the time this testcase was added, the above `float` styling would
+ have automatically forced "display:block" for these elements, so we
+ should preserve that styling to preserve the integrity of the crashtest
+ since blockification behavior for -moz-box is changing. */
+ display: block;
+}
+</html:style>
+
+ <menulist>
+ <menupopup id="ping">
+ </menupopup>
+ </menulist>
+
+</window>
diff --git a/layout/base/crashtests/360339-2.xhtml b/layout/base/crashtests/360339-2.xhtml
new file mode 100644
index 0000000000..e16138fde2
--- /dev/null
+++ b/layout/base/crashtests/360339-2.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0" ?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css" ?>
+
+<window xmlns:html="http://www.w3.org/1999/xhtml"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<html:style>
+* { float: right; }
+#ping { float: none; }
+
+*:not(#ping):not(style) {
+ /* At the time this testcase was added, the above `float` styling would
+ have automatically forced "display:block" for these elements, so we
+ should preserve that styling to preserve the integrity of the crashtest
+ since blockification behavior for -moz-box is changing. */
+ display: block;
+}
+</html:style>
+
+<hbox>
+ <menulist>
+ <menupopup id="ping">
+ <menuitem label="1"/>
+ </menupopup>
+ </menulist>
+</hbox>
+
+</window>
diff --git a/layout/base/crashtests/363729-1.html b/layout/base/crashtests/363729-1.html
new file mode 100644
index 0000000000..a48cf55d3d
--- /dev/null
+++ b/layout/base/crashtests/363729-1.html
@@ -0,0 +1,3 @@
+<html class="reftest-paged">
+<body>
+<b onfocus="event.target.setAttribute('tabindex', Math.floor(Math.random()*5)-9)"display: inline-table;position: fixed;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: always;page-break-inside: inherit; style="display: inline-table;position: fixed;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: always;page-break-inside: inherit;"><sup rowspan="12"display: table-caption;position: static;overflow: clip;float: auto;direction: ltr;page-break-before: avoid;page-break-after: right;page-break-inside: inherit; style="display: table-caption;position: static;overflow: clip;float: auto;direction: ltr;page-break-before: avoid;page-break-after: right;page-break-inside: inherit;"><bdo onfocus="event.target.parentNode.removeChild(event.target)"display: table-footer-group;position: absolute;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: right;page-break-inside: auto; style="display: table-footer-group;position: absolute;overflow: hidden;float: left;direction: ltr;page-break-before: right;page-break-after: right;page-break-inside: auto;"><dir tabindex="12"display: -moz-box;position: static;overflow: auto;float: left;direction: ltr;page-break-before: avoid;page-break-after: inherit;page-break-inside: inherit; style="display: -moz-box;position: static;overflow: auto;float: left;direction: ltr;page-break-before: avoid;page-break-after: inherit;page-break-inside: inherit;"><i rowspan="1"display: -moz-stack;position: fixed;overflow: visible;float: right;direction: rtl;page-break-before: right;page-break-after: always;page-break-inside: avoid; style="display: -moz-stack;position: fixed;overflow: visible;float: right;direction: rtl;page-break-before: right;page-break-after: always;page-break-inside: avoid;"><select colspan="1"display: block;position: absolute;overflow: hidden;float: right;direction: auto;page-break-before: auto;page-break-after: avoid;page-break-inside: auto; style="display: block;position: absolute;overflow: hidden;float: right;direction: auto;page-break-before: auto;page-break-after: avoid;page-break-inside: auto;"></abbr></var></u></base></em></button></optgroup></menu></body>
diff --git a/layout/base/crashtests/363729-2.html b/layout/base/crashtests/363729-2.html
new file mode 100644
index 0000000000..839d70456b
--- /dev/null
+++ b/layout/base/crashtests/363729-2.html
@@ -0,0 +1,18 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase Bug 363729 – Crash [@ nsIFrame::GetPositionIgnoringScrolling] on print preview that uses position: fixed</title>
+</head>
+<body>
+This page should not crash on print preview
+<span style="position: fixed; page-break-after: always;"></span>
+<dir>
+<span style="display: inline-table; position: fixed; page-break-after: always;">
+
+<span style="position: absolute;">
+<span style=" position: fixed;"></span>
+</span>
+
+</span>
+</dir>
+</body>
+</html>
diff --git a/layout/base/crashtests/363729-3.html b/layout/base/crashtests/363729-3.html
new file mode 100644
index 0000000000..296d5e9cc5
--- /dev/null
+++ b/layout/base/crashtests/363729-3.html
@@ -0,0 +1,20 @@
+<html class="reftest-paged">
+<head>
+<title>Testcase Bug 363729 – Crash [@ nsIFrame::GetPositionIgnoringScrolling] on print preview that uses position: fixed (Branch version)</title>
+</head>
+<body>
+This page should not crash on print preview
+<span style="page-break-after: always;"></span>
+
+<dir>
+ <table style="position: fixed; page-break-after: always;">
+ <tr><td>
+ <span style="position: absolute;">
+ <span style=" position: fixed;"></span>
+ </span>
+ </td></tr>
+ </table>
+</dir>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/365909-1.xhtml b/layout/base/crashtests/365909-1.xhtml
new file mode 100644
index 0000000000..e543f0927e
--- /dev/null
+++ b/layout/base/crashtests/365909-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head></head>
+
+<body onload="document.getElementById('tbody').appendChild(document.createTextNode('Bar'));">
+ <p>Reload to see the assertion failure.</p>
+ <div><span dir="rtl">Foo<tbody id="tbody"></tbody></span></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/365909-2.xhtml b/layout/base/crashtests/365909-2.xhtml
new file mode 100644
index 0000000000..73ffa4a344
--- /dev/null
+++ b/layout/base/crashtests/365909-2.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head></head>
+
+<body onload="document.getElementById('td').appendChild(document.createTextNode('Bar'));">
+ <p>Reload to see the assertion failure.</p>
+ <div><span dir="rtl">Foo<td id="td"></td></span></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/366128-1.xhtml b/layout/base/crashtests/366128-1.xhtml
new file mode 100644
index 0000000000..66c985c5a7
--- /dev/null
+++ b/layout/base/crashtests/366128-1.xhtml
@@ -0,0 +1,32 @@
+<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 doomedOption = document.getElementById("doomedOption");
+ var floated = document.getElementById("floated");
+
+ doomedOption.parentNode.removeChild(doomedOption);
+ floated.removeAttributeNS(null, "style");
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+ <select>
+ <xul:label>
+ <option id="doomedOption">M</option>
+ <span id="floated" style="float: right;"/>
+ </xul:label>
+ </select>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/366271-1-frame.svg b/layout/base/crashtests/366271-1-frame.svg
new file mode 100644
index 0000000000..8ba0dc5992
--- /dev/null
+++ b/layout/base/crashtests/366271-1-frame.svg
@@ -0,0 +1,13 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ onload="setTimeout(function() { document.getElementById('focc').style.overflow = 'scroll'; setTimeout(function() { location.reload(); }, 200); }, 200);">
+
+ <g id="focc">
+ <foreignObject width="500" height="500" id="fo" x="20" y="20">
+ <div xmlns="http://www.w3.org/1999/xhtml">
+ <select><option>Reload to see some assertions</option></select>
+ </div>
+ </foreignObject>
+ </g>
+
+</svg>
diff --git a/layout/base/crashtests/366271-1.html b/layout/base/crashtests/366271-1.html
new file mode 100644
index 0000000000..eb89acfd98
--- /dev/null
+++ b/layout/base/crashtests/366271-1.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var childLoads = 0;
+function inc()
+{
+ ++childLoads;
+ if (childLoads >= 2)
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<iframe src="366271-1-frame.svg" onload="inc();"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/366967-1.html b/layout/base/crashtests/366967-1.html
new file mode 100644
index 0000000000..f8e63d96f6
--- /dev/null
+++ b/layout/base/crashtests/366967-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
+ "http://www.w3.org/TR/html4/loose.dtd">
+<html class="reftest-wait">
+<head>
+
+<style>
+#cat { float: left; }
+#zebra { background: lightgreen; }
+#zebra:after { content: "a b c d e"; }
+#zebra:first-letter { display: none; }
+</style>
+
+<script>
+function boom1()
+{
+ document.getElementById("cat").style.outline = "1px solid yellow";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("cat").style.overflow = "auto";
+ document.documentElement.removeAttribute("class")
+}
+</script>
+
+</head>
+
+<body onload="setTimeout(boom1, 30)" style="overflow: scroll">
+ <div id="zebra"><b id="cat">Cat</b></div>
+ <div style="direction: rtl">This is an RTL div</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/367015-1.html b/layout/base/crashtests/367015-1.html
new file mode 100644
index 0000000000..d1fe1c5f66
--- /dev/null
+++ b/layout/base/crashtests/367015-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+
+<style>
+html:first-line { }
+body { direction: rtl; float: right; }
+</style>
+
+<script>
+function boom()
+{
+ document.body.style.cssFloat = "none";
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+<p>Hello world</p>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/367243-1.html b/layout/base/crashtests/367243-1.html
new file mode 100644
index 0000000000..23910438b1
--- /dev/null
+++ b/layout/base/crashtests/367243-1.html
@@ -0,0 +1,37 @@
+<html class="reftest-wait">
+<head>
+
+<style id="style">
+.ch1 { counter-increment: chicken; }
+</style>
+
+<script>
+function boom()
+{
+ document.getElementsByTagName("ol")[0].setAttribute("class", "wtf");
+ document.getElementById("style").textContent = ".ch2 { counter-increment: chicken; }";
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+
+<ol>
+ <li class="ch1">item</li>
+ <li>item
+ <ol>
+ <li class="ch2">item</li>
+ </ol>
+ </li>
+</ol>
+
+<ol class="ch2">
+ <li>item</li>
+</ol>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/369176-1.html b/layout/base/crashtests/369176-1.html
new file mode 100644
index 0000000000..536206c46d
--- /dev/null
+++ b/layout/base/crashtests/369176-1.html
@@ -0,0 +1,37 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+
+<script>
+
+function boom()
+{
+ document.getElementById("f").className = 'q';
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<style>
+
+body {
+ width: 10em;
+}
+
+#f:after {
+ content: "TTT";
+}
+
+</style>
+
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<span id="f">foo foo foo foo foo foo foo foo foo foo<span style="display: block"></span></span>
+
+
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/369547-1.html b/layout/base/crashtests/369547-1.html
new file mode 100644
index 0000000000..6820cfc365
--- /dev/null
+++ b/layout/base/crashtests/369547-1.html
@@ -0,0 +1,50 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsSubDocumentFrame::Reflow] with testcase, using first-letter, first-line, inline-block and iframes</title>
+</head>
+<body>
+<div style="width:1440px;" id="a">
+<div>
+<fieldset>
+
+<legend style="display: inline-block;"></legend>
+<span></span>
+<iframe></iframe>
+<iframe></iframe>
+<legend style="display: list-item;">
+<iframe></iframe>
+</legend>
+</fieldset>
+</div>
+</div>
+<script>
+function addfirstline(){
+var x=document.createElementNS('http://www.w3.org/1999/xhtml','style');
+x.innerHTML='\
+#a *::first-letter { }\
+#a *::first-line {}\
+#a *::after { content:"anonymous text"; text-transform: uppercase;height: 90%;}\
+#a *::before { content:"before text"; font-size: 10px;}\
+';
+document.documentElement.appendChild(x);
+}
+setTimeout(addfirstline,200);
+
+var j=0;
+function replacestyles(i){
+var x=document.getElementById('a').getElementsByTagName('*');
+if (j>=2) return;
+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;j++;}
+ i++;
+setTimeout(replacestyles,50,i);
+}
+setTimeout(replacestyles,500,0);
+</script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/369547-2.html b/layout/base/crashtests/369547-2.html
new file mode 100644
index 0000000000..d3e7f2758c
--- /dev/null
+++ b/layout/base/crashtests/369547-2.html
@@ -0,0 +1,15 @@
+<html><head><script>
+function doe2() {
+document.getElementById('a').setAttribute('style', 'display: inline-block;');
+document.body.offsetHeight;
+document.getElementById('b').removeAttribute('style');
+document.body.offsetHeight;
+}
+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> \ No newline at end of file
diff --git a/layout/base/crashtests/369945-1.xhtml b/layout/base/crashtests/369945-1.xhtml
new file mode 100644
index 0000000000..24d07f9d3f
--- /dev/null
+++ b/layout/base/crashtests/369945-1.xhtml
@@ -0,0 +1,42 @@
+<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()
+{
+ z = document.getElementById("z");
+ p = z.parentNode;
+ p.appendChild(z);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="setTimeout(boom, 10)">
+
+<p>
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="inline">
+ <mi>x
+ <xul:scrollbar>
+ <xul:hbox>
+ <xul:hbox id="z">
+ <mfrac>
+ <mn>1</mn>
+ <mn>2</mn>
+ </mfrac>
+ </xul:hbox>
+ </xul:hbox>
+ </xul:scrollbar>
+ </mi>
+ </math>
+</p>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/371681-1.xhtml b/layout/base/crashtests/371681-1.xhtml
new file mode 100644
index 0000000000..4f3b95653f
--- /dev/null
+++ b/layout/base/crashtests/371681-1.xhtml
@@ -0,0 +1,22 @@
+<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 y = document.getElementById("y");
+ y.parentNode.removeChild(y);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+
+</head>
+<body onload="setTimeout(boom, 30);">
+
+<div style="float: left">X<xul:hbox><input type="radio"/></xul:hbox></div>
+<div id="y" style="float: left">Y</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/372237-1.html b/layout/base/crashtests/372237-1.html
new file mode 100644
index 0000000000..84301461f7
--- /dev/null
+++ b/layout/base/crashtests/372237-1.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("b").style.overflow = "hidden";
+ setTimeout(boom2, 30);
+}
+
+function boom2()
+{
+ document.getElementById("g").style.display = "none";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div style="float: left;">
+<div id="b" style="display: -moz-box; border: 1px solid black;"><img width="16" height="16" src="../../../testing/crashtest/images/tree.gif"/></div>
+<div style="position: fixed;"></div>
+</div>
+
+<div id="g" style="display: inline"><div></div></div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/372550-1.html b/layout/base/crashtests/372550-1.html
new file mode 100644
index 0000000000..778652f0b5
--- /dev/null
+++ b/layout/base/crashtests/372550-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <style>
+ div#x::first-letter { color: blue; }
+ </style>
+</head>
+<body>
+<div id="x">x</div>
+<script>
+ document.body.offsetWidth;
+ var div = document.getElementById("x");
+ div.id = "y";
+ div.firstChild.remove();
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/373628-1.html b/layout/base/crashtests/373628-1.html
new file mode 100644
index 0000000000..2ce99cdfc4
--- /dev/null
+++ b/layout/base/crashtests/373628-1.html
@@ -0,0 +1,16 @@
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 373628</title>
+<script>
+function stop() {
+ document.body.removeChild(document.body.children[0]);
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload='setTimeout(stop, 1000)'>
+
+<iframe src="373628.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/373628.html b/layout/base/crashtests/373628.html
new file mode 100644
index 0000000000..749af86b9c
--- /dev/null
+++ b/layout/base/crashtests/373628.html
@@ -0,0 +1,929 @@
+<html>
+<head>
+<script>
+function doe() {
+window.location.reload();
+}
+</script>
+</head>
+
+<body style=" display: table-cell; direction: ltr;" onload="setTimeout(doe, 0);">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<nobr style=" display: -moz-box; direction: rtl;">
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: -moz-box; position: absolute; direction: rtl;">
+</s>
+</q>
+<pre style=" display: -moz-inline-box; position: absolute; direction: ltr;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-box; position: absolute; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</q>
+<ol style=" display: block; direction: rtl;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-box; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+</q>
+</s>
+</q>
+
+</ol>
+</pre>
+</nobr>
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-box; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+<s style=" display: -moz-box; position: absolute; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</q>
+</s>
+</q>
+</s>
+</q>
+<p style=" display: table; position: absolute; direction: ltr;">
+</p>
+<pre style=" display: inline; position: absolute; float: left; direction: ltr;">
+<q style=" display: table-header-group; ">
+<s style=" display: -moz-box; position: absolute; direction: rtl;">
+<q style=" display: table-cell; ">
+<s style=" display: -moz-box; position: absolute; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</q>
+</s>
+<map style=" display: inline-table; position: fixed; direction: ltr;">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</map>
+</q>
+</s>
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<samp style=" display: inherit; position: fixed;">
+</samp>
+<p>
+</p>
+<p style=" display: -moz-box; direction: rtl;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: inline; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+<bdo style=" display: -moz-inline-box; direction: ltr;">
+</bdo>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<p style=" direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<bdo style=" display: -moz-inline-box; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+<q style=" display: -moz-box; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<ol style=" display: inline-block; float: left; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<nobr style=" direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: table-column; position: absolute; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+<p>
+</p>
+<bdo style=" display: table-column; position: absolute; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</bdo>
+</bdo>
+</nobr>
+<samp style=" display: table-cell; position: absolute; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<small style=" position: fixed;">
+<nobr style=" display: table-footer-group; direction: ltr;">
+<samp style=" display: table-row-group; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</nobr>
+</small>
+<samp style=" display: -moz-inline-box; direction: rtl;">
+<bdo style=" display: -moz-box;">
+<samp style=" display: table-row; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</bdo>
+</samp>
+</s>
+</bdo>
+</bdo>
+</samp>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<pre style=" display: table-row; direction: ltr;">
+<samp style=" display: -moz-inline-box; direction: rtl;">
+<map style=" display: -moz-box; position: fixed; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</map>
+</samp>
+</pre>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m m
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p style=" display: table-caption; ">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<s style=" position: fixed; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<s style=" position: fixed; direction: rtl;">
+<s style=" display: -moz-inline-box; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<s style=" position: fixed; direction: rtl;">
+<s style=" display: -moz-inline-box; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<bdo style="overflow: hidden;">
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<ol style=" display: -moz-inline-box; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<s style=" position: fixed; direction: rtl;">
+<bdo style="overflow: hidden; ">
+<q style=" display: list-item; direction: ltr;">
+</q>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p style=" display: block; position: fixed; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<s style=" position: fixed; direction: rtl;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</s>
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</ol>
+</ol>
+</pre>
+<listing style=" display: -moz-inline-box;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</listing>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<samp style=" display: inherit; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<nobr style=" display: -moz-inline-box; position: absolute; direction: ltr;">
+<ol style=" display: table-caption; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<map style=" display: list-item; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<listing style=" display: -moz-inline-box; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style="overflow: auto; display: inline-table; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: table-column-group; direction: rtl;">
+<samp style="display: inline; " display:="" inline-table;position:="" fixed;overflow:="" right;direction:="" ltr;="">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</s>
+</s>
+</listing>
+</ol>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</s>
+</s>
+<p>
+</p>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<small style=" display: -moz-inline-box; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+
+
+
+</small>
+</bdo>
+</s>
+</s>
+</nobr>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-box; position: fixed; direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</small>
+</bdo>
+</s>
+</s>
+</samp>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<ol style=" display: block; direction: ltr;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-box; position: fixed; direction: ltr;">
+<q style=" display: table-row; direction: rtl;">
+<samp style=" direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</q>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p style=" display: -moz-inline-box; direction: rtl;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-box; position: fixed; direction: ltr;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<small style=" display: -moz-inline-box; position: fixed; direction: ltr;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<s style=" display: -moz-box; position: fixed;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</body>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>
+<ol style="display: inline-block;direction: ltr;">
+<p style="display: inherit;position: fixed;direction: ltr;">
+<body style="display: position: absolute;direction: ltr;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</ol>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</html>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</map>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</body>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</p>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</pre>
+<legend style="display: -moz-box;position: fixed;direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</small>
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</ol>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<pre style=" display: table-cell; position: absolute;">
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<q style=" display: list-item; direction: ltr;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</q>
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</q>
+<nobr style=" display: inherit; position: fixed; direction: rtl;">
+<q style=" display: -moz-box; direction: rtl;">
+<s style=" display: table; position: absolute; direction: ltr;">
+<q style=" display: -moz-box; position: absolute;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</q>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<s style=" display: inherit; position: fixed; direction: rtl;">
+<small style=" display: -moz-box;">
+</small>
+</s>
+</q>
+<ol style=" display: -moz-box; direction: rtl;">
+<pre style=" display: table-column-group; position: absolute;">
+<q style=" display: -moz-box; direction: rtl;">
+<s style=" display: inherit; position: fixed; direction: rtl;">
+<small style=" display: -moz-box;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</small>mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</s>
+<samp style=" display: table-column-group; ">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</samp>
+</q>
+</pre>
+</ol>
+<q style=" display: -moz-box; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</q>
+<p>
+</p>
+<q style=" display: -moz-box; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+<samp style=" display: inline-block; ">
+</samp>
+</bdo>
+</q>
+<p style=" display: -moz-box; position: absolute; direction: rtl;">
+<q style=" display: -moz-box; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m mm m
+</bdo>
+</bdo>
+</q>
+</p>
+</nobr>
+</listing>
+</listing>
+</nobr>
+</small>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</s>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+</pre>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+
+</bdo>
+</bdo>
+</s>
+</s>
+</bdo>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</bdo>
+</s>
+</bdo>
+</q>
+</q>
+</bdo>
+</q>
+</q>
+</q>
+<p>
+</p>
+<q style=" display: table-header-group; ">
+<q style=" display: table-cell; ">
+<q style=" display: table; direction: ltr;">
+<bdo style=" display: block;">
+<q style=" display: -moz-box; direction: rtl;">
+<q style=" display: -moz-inline-box; position: absolute; direction: rtl;">
+<bdo style=" display: -moz-box; direction: rtl;">
+<s style=" display: table-row; position: fixed; direction: rtl;">
+<bdo style=" display: table-column; position: absolute; direction: ltr;">
+<bdo style=" display: inline-table; direction: ltr;">
+<s style=" display: table-cell;">
+<bdo style=" display: -moz-box;">
+<bdo style="overflow: scroll; display: -moz-box; float: right;">
+<bdo style="overflow: hidden;">
+<s style="overflow: auto; display: inline-table; ">
+<s style=" display: table-column-group; direction: rtl;">
+<bdo style=" display: inline-block; direction: rtl;">
+<bdo style=" display: table-row-group;">
+</body>
+</html>
+
diff --git a/layout/base/crashtests/374297-1.html b/layout/base/crashtests/374297-1.html
new file mode 100644
index 0000000000..6ff2bc3f49
--- /dev/null
+++ b/layout/base/crashtests/374297-1.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom() {
+ var newNode = document.createElementNS("http://www.w3.org/1999/xhtml", 'table');
+ document.getElementById('td').appendChild(newNode);
+ document.getElementById('table2').setAttribute('align', 'right');
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="setTimeout(boom,30)">
+
+<table id="table2"><tr><td id="td"></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/374297-2.html b/layout/base/crashtests/374297-2.html
new file mode 100644
index 0000000000..86aeae8cea
--- /dev/null
+++ b/layout/base/crashtests/374297-2.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom() {
+ var $table2 = document.getElementById('table2');
+ $table2.setAttribute('width', '30%');
+ var $th273 = document.getElementById('th273');
+ $th273.style.position = "relative";
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+
+<body onload="setTimeout(boom,30)">
+
+<table id="table2"><tr><div><th id="th273"></th></div></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/378325-1.html b/layout/base/crashtests/378325-1.html
new file mode 100644
index 0000000000..37426875ad
--- /dev/null
+++ b/layout/base/crashtests/378325-1.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+<head>
+<title>Testcase bug - Crash [@ PresShell::FlushPendingNotifications] when removing window on focus and then reappearing again</title>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+</head>
+<body>
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Cbody%20tabindex%3D%221%22%20onfocus%3D%22top.doe2%28%29%3Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%22%3E%0A%3Cscript%3E%0AsetTimeout%28function%28%29%7Bdocument.body.focus%28%29%7D%2C%20200%29%3B%0A%3C/script%3E%3C/body%3E%3C/html%3E" id="content"></iframe>
+
+<script>
+function doe() {
+ if (!document.getElementById('content')) {
+ var y = document.createElement('iframe');
+ y.id = 'content';
+ y.src = 'data:text/html;charset=utf-8,%3Chtml%3E%3Cbody%20tabindex%3D%221%22%20onfocus%3D%22top.doe2%28%29%3Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%22%3E%0A%3Cscript%3E%0AsetTimeout%28function%28%29%7Bdocument.body.focus%28%29%7D%2C%20200%29%3B%0A%3C/script%3E%3C/body%3E%3C/html%3E';
+ document.body.appendChild(y);
+ }
+}
+
+ function doe2() {
+ setInterval(doe, 200);
+ }
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/378682.html b/layout/base/crashtests/378682.html
new file mode 100644
index 0000000000..2f4bf8dc77
--- /dev/null
+++ b/layout/base/crashtests/378682.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+<title>Testcase bug - Crash [@ nsPresContext::GetContainerInternal] when removing window on focus and reloading</title>
+</head>
+<body>
+This page should not crash Mozilla
+<iframe src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%3E%0A%3Ciframe%3E%3C/iframe%3E%0A%3Cscript%3E%0Awindow.frames%5B0%5D.focus%28%29%3B%0AsetTimeout%28doe%2C%20200%29%3B%0Afunction%20doe%28%29%20%7B%0Awindow.frames%5B0%5D.location.reload%28%29%3B%0A%7D%0Afunction%20doe2%28%29%20%7B%0Awindow.addEventListener%28%27focus%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%20%7D%2C%20true%29%3B%0A%7D%0AsetTimeout%28doe2%2C%2050%29%3B%0A%3C/script%3E%0A%3C/body%3E%0A%3C/html%3E"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/379419-1.xhtml b/layout/base/crashtests/379419-1.xhtml
new file mode 100644
index 0000000000..406876160a
--- /dev/null
+++ b/layout/base/crashtests/379419-1.xhtml
@@ -0,0 +1,12 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+<table border="1">
+ <tr>
+ <td>Foo</td>
+ </tr>
+ <thead style="display: block;"></thead>
+</table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/379799-1.html b/layout/base/crashtests/379799-1.html
new file mode 100644
index 0000000000..314744f786
--- /dev/null
+++ b/layout/base/crashtests/379799-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style id="firstLetterSheet">
+ .fl:first-letter { }
+</style>
+
+<style id="emptySheet">
+</style>
+
+<script>
+
+function boom()
+{
+ document.getElementById("firstLetterSheet").textContent = "";
+ document.getElementById("emptySheet").textContent = ".aft:after { content: counter(chicken); }";
+}
+
+</script>
+
+</head>
+
+<body onload="boom()">
+
+<div class="fl">Foo <span class="aft">Bar</span></div>
+
+<p class="aft">Baz</p>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/380096-1.html b/layout/base/crashtests/380096-1.html
new file mode 100644
index 0000000000..53100674cd
--- /dev/null
+++ b/layout/base/crashtests/380096-1.html
@@ -0,0 +1,4 @@
+<html style="display: inline-table">
+<head style="display: table-caption"></head>
+<body onload="document.body.style.cssFloat = 'left';"></body>
+</html>
diff --git a/layout/base/crashtests/382204-1.html b/layout/base/crashtests/382204-1.html
new file mode 100644
index 0000000000..0ecac4cc70
--- /dev/null
+++ b/layout/base/crashtests/382204-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+
+<html style="display: table;" class="reftest-wait">
+
+<head>
+<script>
+function boom()
+{
+ document.documentElement.style.color = "blue";
+ document.getElementById("zeta").style.display = "inline";
+
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+ <div id="zeta">foo</div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/383129-1-inner.xhtml b/layout/base/crashtests/383129-1-inner.xhtml
new file mode 100644
index 0000000000..c4bcf95ae1
--- /dev/null
+++ b/layout/base/crashtests/383129-1-inner.xhtml
@@ -0,0 +1,22 @@
+<treerow xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="zebra">
+<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function doe(){
+ document.getElementById('b').remove();
+ document.getElementById('c').remove();
+}
+
+setTimeout(doe, 200);
+]]></script>
+
+<box id="a"/>
+<mtr xmlns="http://www.w3.org/1998/Math/MathML">
+<box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" class="zebra" id="c"/>
+</mtr>
+<box style="display: inline;" id="b"/>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a { counter-reset: chicken 11 egg; }
+#b { counter-increment: chicken -1 egg; }
+*[class=zebra] { counter-increment: chicken 5; }
+</style>
+</treerow> \ No newline at end of file
diff --git a/layout/base/crashtests/383129-1.html b/layout/base/crashtests/383129-1.html
new file mode 100644
index 0000000000..ff1ec7a2a3
--- /dev/null
+++ b/layout/base/crashtests/383129-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="383129-1-inner.xhtml"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/384344-1-inner.html b/layout/base/crashtests/384344-1-inner.html
new file mode 100644
index 0000000000..81355759b0
--- /dev/null
+++ b/layout/base/crashtests/384344-1-inner.html
@@ -0,0 +1,20 @@
+<table ><td id="mytd"><small>
+</a>&nbsp-
+<a >A9
+<a id="mya1">AOL
+
+
+
+
+
+
+
+<a id="mya2">Yahoo
+
+<script>
+ mytd.style.display = "-moz-grid";
+ mya2.style.display = "list-item";
+ mya1.style.cssFloat = "right";
+ setTimeout('mya1.style.overflow = "scroll"',100);
+</script>
+ \ No newline at end of file
diff --git a/layout/base/crashtests/384344-1.html b/layout/base/crashtests/384344-1.html
new file mode 100644
index 0000000000..ea509bb52c
--- /dev/null
+++ b/layout/base/crashtests/384344-1.html
@@ -0,0 +1,9 @@
+<html class="reftest-wait">
+<head>
+<script>
+setTimeout('document.documentElement.className = ""', 500);
+</script>
+<body>
+<iframe src="384344-1-inner.html"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/384392-1.xhtml b/layout/base/crashtests/384392-1.xhtml
new file mode 100644
index 0000000000..a5f04c0f1c
--- /dev/null
+++ b/layout/base/crashtests/384392-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var table = document.getElementById("table");
+ document.removeChild(document.documentElement);
+ document.appendChild(table);
+}
+
+</script>
+</head>
+
+<body onload="boom()">
+
+
+
+<table border="1" id="table">
+ <tr>
+ <td><input type="text" value="Textbox" /></td>
+ </tr>
+</table>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/384392-2.svg b/layout/base/crashtests/384392-2.svg
new file mode 100644
index 0000000000..332406749e
--- /dev/null
+++ b/layout/base/crashtests/384392-2.svg
@@ -0,0 +1,3 @@
+<circle xmlns="http://www.w3.org/2000/svg">
+ <foreignObject/>
+</circle> \ No newline at end of file
diff --git a/layout/base/crashtests/384649-1.xhtml b/layout/base/crashtests/384649-1.xhtml
new file mode 100644
index 0000000000..e2ba50cdee
--- /dev/null
+++ b/layout/base/crashtests/384649-1.xhtml
@@ -0,0 +1,31 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+/* use attribute selector instead of the .class shorthand to work around bug 379178 */
+
+*[class="fixed"] { position: fixed; }
+
+math, mtable, mtr { position: inherit; }
+
+</style>
+</head>
+
+<body>
+
+<div class="fixed">
+ <math xmlns="http://www.w3.org/1998/Math/MathML" display="block">
+ <mtable>
+ <mtr class="fixed">
+ <mtd><mi>x</mi></mtd>
+ </mtr>
+ <mtr>
+ <mtd><mi>y</mi></mtd>
+ </mtr>
+ </mtable>
+ </math>
+</div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/385354.html b/layout/base/crashtests/385354.html
new file mode 100644
index 0000000000..7c5a6a0c3d
--- /dev/null
+++ b/layout/base/crashtests/385354.html
@@ -0,0 +1,18 @@
+<html><head>
+<style>
+object::before { content:"before text";}
+</style>
+<script>
+function doe(){
+document.getElementById('a').setAttribute('style', 'overflow: scroll; font-family: Hiragino Kaku Gothic Std;');
+}
+setTimeout(doe,500);
+</script>
+</head>
+<body>
+<div style="text-align: right;width: -moz-intrinsic;">
+<object style="white-space: -moz-pre-wrap; word-spacing: 10px;"><span id="a">
+</span></object>
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/385866-1.xhtml b/layout/base/crashtests/385866-1.xhtml
new file mode 100644
index 0000000000..7ef6620c7b
--- /dev/null
+++ b/layout/base/crashtests/385866-1.xhtml
@@ -0,0 +1,23 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<style>
+div, col { counter-reset: chicken; }
+</style>
+
+<script>
+function boom()
+{
+ var col = document.getElementById("col");
+ col.parentNode.removeChild(col);
+}
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div><col id="col" span="2"></col></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/385880-1.xhtml b/layout/base/crashtests/385880-1.xhtml
new file mode 100644
index 0000000000..7c78da7cc8
--- /dev/null
+++ b/layout/base/crashtests/385880-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">
+
+<body>
+<table><xul:menubar style="display: table;" /></table>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/386266-1.html b/layout/base/crashtests/386266-1.html
new file mode 100644
index 0000000000..82bf8de471
--- /dev/null
+++ b/layout/base/crashtests/386266-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style>
+#outer {
+ column-count: 2;
+}
+#inner {
+ border: 1px solid green;
+}
+</style>
+
+<style id="s">
+#inner {
+ float: right;
+ height: 1em;
+}
+</style>
+
+</head>
+
+<body onload="document.getElementById('s').disabled = true;">
+
+<div id="outer"><div id="inner"></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/386476.html b/layout/base/crashtests/386476.html
new file mode 100644
index 0000000000..744ee85e87
--- /dev/null
+++ b/layout/base/crashtests/386476.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>bug 386476</title>
+ </head>
+ <body onload="setTimeout(function(){document.querySelector('textarea').setAttribute('dir','rtl')},0)">
+<textarea rows="8" cols="50" dir="ltr">text inside a textarea gone wild
+second line
+[url=http://bugzilla.mozilla.org/]טקסט[/url], [url=http://bugzilla.mozilla.org/]url[טקסט], [url=http://bugzilla.mozilla.org/]טקסט[/url].</textarea>
+ </body>
+</html>
diff --git a/layout/base/crashtests/387195-1.html b/layout/base/crashtests/387195-1.html
new file mode 100644
index 0000000000..199c3a055e
--- /dev/null
+++ b/layout/base/crashtests/387195-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<div style="display: table-header-group; text-indent: -20em; border: 1px dotted black;">foo</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/387195-2.xhtml b/layout/base/crashtests/387195-2.xhtml
new file mode 100644
index 0000000000..811f147cbb
--- /dev/null
+++ b/layout/base/crashtests/387195-2.xhtml
@@ -0,0 +1,23 @@
+<!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">
+<title>Testcase for bug </title>
+<head>
+<script type="text/javascript">
+function boom() {
+ var colgroup = document.createElementNS("http://www.w3.org/1999/xhtml", 'colgroup');
+ document.getElementById('thead').insertBefore(colgroup, null);
+}
+</script>
+
+<style type="text/css">
+ thead {border:3px solid purple;}
+</style>
+</head>
+
+
+<body onload="boom()">
+
+<table><thead id="thead"></thead></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/388715-1.html b/layout/base/crashtests/388715-1.html
new file mode 100644
index 0000000000..be09591f8d
--- /dev/null
+++ b/layout/base/crashtests/388715-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<style type="text/css">
+#div:first-letter { float: left; color: lightgreen; }
+</style>
+
+<script type="text/javascript">
+function boom()
+{
+ document.getElementById("div").className = "anything";
+}
+</script>
+</head>
+
+<body onload="boom()">
+
+<div id="div"><span style="color: magenta">Foo</span> bar</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/390976-1.html b/layout/base/crashtests/390976-1.html
new file mode 100644
index 0000000000..4f0f578e21
--- /dev/null
+++ b/layout/base/crashtests/390976-1.html
@@ -0,0 +1,22 @@
+<html>
+
+<head>
+<script>
+function boom()
+{
+ var aaa = document.getElementById("aaa");
+ var bbb = document.getElementById("bbb");
+ aaa.parentNode.insertBefore(bbb, aaa);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div><span><span style="display: table-caption;"></span><span id="aaa"><div></div></span></span></div>
+
+<b id="bbb" style="display: table-caption;"></b>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/393661-1.html b/layout/base/crashtests/393661-1.html
new file mode 100644
index 0000000000..b7ea19cd3c
--- /dev/null
+++ b/layout/base/crashtests/393661-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<style>
+#z:first-letter { float: right; }
+</style>
+<script>
+function boom()
+{
+ var z = document.getElementById("z");
+ z.firstChild.remove();
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="z">abc</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/393801-1-inner.html b/layout/base/crashtests/393801-1-inner.html
new file mode 100644
index 0000000000..b21ab557da
--- /dev/null
+++ b/layout/base/crashtests/393801-1-inner.html
@@ -0,0 +1,781 @@
+<html>
+<body>
+<body style="position: absolute; background: yellow;">
+ <div style="position: absolute; background: lightgreen;">p</div>
+ <div style="display: none;">
+
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+</div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/393801-1.html b/layout/base/crashtests/393801-1.html
new file mode 100644
index 0000000000..bed934eff4
--- /dev/null
+++ b/layout/base/crashtests/393801-1.html
@@ -0,0 +1,7 @@
+<html>
+<head>
+</head>
+<body>
+<iframe scrolling="no" src="393801-1-inner.html" width="200" height="200"></iframe>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/394150-1.xhtml b/layout/base/crashtests/394150-1.xhtml
new file mode 100644
index 0000000000..b2349c9f8b
--- /dev/null
+++ b/layout/base/crashtests/394150-1.xhtml
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var ms = document.createElementNS("http://www.w3.org/1998/Math/MathML", "ms");
+ var textNode = document.getElementById("emptyset").firstChild;
+ var mrow = document.getElementById("mrow");
+
+ ms.appendChild(textNode); // *move* the text node from one place to another!
+ mrow.appendChild(ms);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<math xmlns="http://www.w3.org/1998/Math/MathML">
+<merror><emptyset id="emptyset">
+ <mrow id="mrow"></mrow></emptyset></merror>
+</math>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/397011-1.xhtml b/layout/base/crashtests/397011-1.xhtml
new file mode 100644
index 0000000000..6dff69750a
--- /dev/null
+++ b/layout/base/crashtests/397011-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+
+<div style="text-indent: 11.2px;">
+ <div style="column-count: 2;">
+ <span style="float: left;"></span>
+ </div>
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/398510-1.xhtml b/layout/base/crashtests/398510-1.xhtml
new file mode 100644
index 0000000000..af48c8e5ee
--- /dev/null
+++ b/layout/base/crashtests/398510-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+mtd:first-letter { }
+</style>
+<script>
+function boom()
+{
+ var b = document.body;
+ document.documentElement.removeChild(b);
+ document.documentElement.offsetHeight;
+ document.documentElement.appendChild(b);
+
+ var t = document.getElementById('t');
+ t.removeChild(t.firstChild);
+}
+</script>
+</head>
+<body onload="boom();">
+<mtd xmlns="http://www.w3.org/1998/Math/MathML" id="t">s</mtd>
+</body>
+</html>
diff --git a/layout/base/crashtests/398733-1.html b/layout/base/crashtests/398733-1.html
new file mode 100644
index 0000000000..812ef02039
--- /dev/null
+++ b/layout/base/crashtests/398733-1.html
@@ -0,0 +1,20 @@
+<html><head>
+<script>
+function doe2(i) {
+var x=document.getElementsByTagName('*');
+document.body.setAttribute('style', 'display: inline; position: relative;');
+document.body.offsetHeight;
+document.getElementById('a').setAttribute('style', '');
+document.getElementById('b').setAttribute('style', 'position: absolute;');
+}
+setTimeout(doe2,100);
+</script>
+</head>
+
+<body>
+<span id="b"></span>&#1593;
+<span id="a" style="position: absolute;">&#1593;
+<span style="position: absolute;"></span>
+</span>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/398733-2.html b/layout/base/crashtests/398733-2.html
new file mode 100644
index 0000000000..2f794eb767
--- /dev/null
+++ b/layout/base/crashtests/398733-2.html
@@ -0,0 +1,9 @@
+<html>
+<body style="display: inline; position: relative;">&#1593;
+<span id="a" style="position: absolute;">&#1593;<span style="position: absolute;"></span></span>
+<script>
+document.body.offsetHeight;
+document.getElementById('a').setAttribute('style', '');
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/399132-1.xhtml b/layout/base/crashtests/399132-1.xhtml
new file mode 100644
index 0000000000..cf7f760e88
--- /dev/null
+++ b/layout/base/crashtests/399132-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style id="style">
+.penguin { overflow: hidden; }
+.penguin:first-line { }
+</style>
+<script>
+function boom()
+{
+ document.getElementById("style").textContent += "";
+ document.getElementById("td").className = "penguin";
+}
+</script>
+</head>
+<body onload="boom();"><td id="td">Text</td></body>
+</html>
diff --git a/layout/base/crashtests/399219-1.xhtml b/layout/base/crashtests/399219-1.xhtml
new file mode 100644
index 0000000000..cfee208ea7
--- /dev/null
+++ b/layout/base/crashtests/399219-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+<script>
+function boom()
+{
+ document.getElementById("div").style.display = "none";
+ document.documentElement.style.display = "-moz-inline-box";
+}
+</script>
+</head>
+<body onload="boom();">
+
+<xul:treeitem style="display: -moz-inline-box;"><xul:hbox><span><div id="div"></div></span></xul:hbox></xul:treeitem>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399365-1.html b/layout/base/crashtests/399365-1.html
new file mode 100644
index 0000000000..ab5f2d0214
--- /dev/null
+++ b/layout/base/crashtests/399365-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ document.body.insertBefore(document.createTextNode("y"), document.body.firstChild);
+}
+</script>
+</head>
+
+<body style="white-space: pre; direction: rtl;" onload="boom();">
+e
+0
+ </body>
+
+</html>
diff --git a/layout/base/crashtests/399676-1.xhtml b/layout/base/crashtests/399676-1.xhtml
new file mode 100644
index 0000000000..82b547e5ea
--- /dev/null
+++ b/layout/base/crashtests/399676-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+</head>
+<body>
+<math:mtd><span style="float: right;" /></math:mtd>
+</body>
+</html>
diff --git a/layout/base/crashtests/399687-1.html b/layout/base/crashtests/399687-1.html
new file mode 100644
index 0000000000..dc6e379fcf
--- /dev/null
+++ b/layout/base/crashtests/399687-1.html
@@ -0,0 +1,38 @@
+<html>
+<head>
+<style>
+
+#colset {
+ width: 300pt;
+ height: 2in;
+ column-count: 3;
+ column-gap: 0;
+}
+
+.ocontainer {
+ height: 0;
+}
+
+.overflow {
+ height: 5in;
+}
+
+</style>
+
+<script>
+function boom()
+{
+ var newDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var colset = document.getElementById("colset");
+ colset.insertBefore(newDiv, colset.childNodes[1]);
+}
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<div id="colset"><div class="ocontainer"><div class="overflow"></div></div> </div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399940-1.xhtml b/layout/base/crashtests/399940-1.xhtml
new file mode 100644
index 0000000000..a8bf909677
--- /dev/null
+++ b/layout/base/crashtests/399940-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var textNode = document.createTextNode("a");
+ document.getElementById("mathy").appendChild(textNode);
+ document.documentElement.offsetHeight;
+ textNode.data = "bc";
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<table><span></span><math:mrow id="mathy" /></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399951-1.html b/layout/base/crashtests/399951-1.html
new file mode 100644
index 0000000000..733774d1cd
--- /dev/null
+++ b/layout/base/crashtests/399951-1.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+</head>
+
+<body style="direction: rtl;" onload="document.body.style.direction = 'ltr';">
+
+<div style="white-space: pre;">
+.i
+ h
+ f
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/399994-1.html b/layout/base/crashtests/399994-1.html
new file mode 100644
index 0000000000..3237434069
--- /dev/null
+++ b/layout/base/crashtests/399994-1.html
@@ -0,0 +1,11 @@
+<html class="reftest-paged">
+<head>
+</head>
+<body>
+
+<div style="display: table; position: fixed;">
+ <div style="display: table-row; page-break-after: always;"></div>
+ <div style="display: table-row;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/400445-1.xhtml b/layout/base/crashtests/400445-1.xhtml
new file mode 100644
index 0000000000..9cb71dbbd6
--- /dev/null
+++ b/layout/base/crashtests/400445-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ var mtd1 = document.getElementById("mtd1");
+ var mtd2 = document.getElementById("mtd2");
+ var newSpan = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+
+ mtd1.appendChild(newSpan);
+ mtd1.removeAttribute("columnspan");
+ mtd2.setAttribute("columnspan", 0);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<math:mtd id="mtd1" columnspan="5" /><math:mtd id="mtd2" />
+</body>
+</html>
diff --git a/layout/base/crashtests/400904-1.xhtml b/layout/base/crashtests/400904-1.xhtml
new file mode 100644
index 0000000000..a00f42fd02
--- /dev/null
+++ b/layout/base/crashtests/400904-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:math="http://www.w3.org/1998/Math/MathML">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+ var mtd = document.getElementById("mtd");
+ var n = document.createElementNS(MATHML_NS, 'mrow');
+ mtd.appendChild(n);
+ mtd.setAttribute('rowspan', 7);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+<math:mtd id="mtd"></math:mtd><math:mtr><math:mrow></math:mrow></math:mtr>
+</body>
+</html>
diff --git a/layout/base/crashtests/401734-1.html b/layout/base/crashtests/401734-1.html
new file mode 100644
index 0000000000..3737b57a53
--- /dev/null
+++ b/layout/base/crashtests/401734-1.html
@@ -0,0 +1,17 @@
+<html><head>
+<script>
+function doe(){
+document.getElementById('a').style.display = 'none';
+}
+</script>
+</head>
+<body onload="document.body.offsetHeight; setTimeout(doe,0)">
+<div style="column-count: 2;width: 400px;">
+<span id="a">
+<span style="float: left; column-width: 100px;">
+&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;-&#1593;
+</span>
+</span>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/401734-2.html b/layout/base/crashtests/401734-2.html
new file mode 100644
index 0000000000..67a3019594
--- /dev/null
+++ b/layout/base/crashtests/401734-2.html
@@ -0,0 +1,17 @@
+<html><head>
+<script>
+function doe(){
+document.getElementById('a').style.display = 'none';
+}
+</script>
+</head>
+<body onload="document.body.offsetHeight; setTimeout(doe,0)">
+<div style="column-count: 2;width: 400px;">
+<span id="a">
+<span style="float: left; column-width: 100px;">
+a-a-a-a-a-a-a-a-a-a-a-a-a-a
+</span>
+</span>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/403048.html b/layout/base/crashtests/403048.html
new file mode 100644
index 0000000000..c41018222e
--- /dev/null
+++ b/layout/base/crashtests/403048.html
@@ -0,0 +1,10 @@
+<html><head></head>
+<body>
+<basefont style="position: absolute;">
+<div id="a" tabindex="1"><span style="position: absolute;"></span>
+</div>
+<script>
+var y=document.getElementById('a');
+y.focus();
+</script>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/403175-1.html b/layout/base/crashtests/403175-1.html
new file mode 100644
index 0000000000..a1c02a5539
--- /dev/null
+++ b/layout/base/crashtests/403175-1.html
@@ -0,0 +1,30 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+var i = 0;
+
+function boom()
+{
+ ++i;
+
+ while (document.body.firstChild)
+ document.body.firstChild.remove();
+
+ var table = document.createElement("table");
+ document.body.appendChild(table);
+ document.documentElement.style.color = (i % 2) ? "red" : "magenta";
+ table.setAttribute("align", "right");
+
+ setTimeout(boom, 15);
+}
+
+function cont()
+{
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+<body onload="boom(); setTimeout(cont, 1000);"></body>
+</html>
diff --git a/layout/base/crashtests/403245-1.html b/layout/base/crashtests/403245-1.html
new file mode 100644
index 0000000000..5c5f731493
--- /dev/null
+++ b/layout/base/crashtests/403245-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<style>
+
+#outer { float: left; }
+#outer:first-letter { float: left; color: magenta; }
+
+</style>
+</head>
+
+<body onload="document.getElementById('inner').style.counterReset = 'chicken';">
+
+<div id="outer"><div id="inner"></div>xy</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403454.html b/layout/base/crashtests/403454.html
new file mode 100644
index 0000000000..14648f6f48
--- /dev/null
+++ b/layout/base/crashtests/403454.html
@@ -0,0 +1,37 @@
+<html class="reftest-wait">
+<head>
+
+<style>
+
+.dddd:before {
+ content: "generated";
+}
+
+</style>
+
+<script>
+
+function b()
+{
+ document.getElementById("float").style.cssFloat = "";
+ setTimeout(b2, 30);
+}
+
+// This is just for visual effect, to make the timing clear.
+// It's not needed for the crash.
+function b2()
+{
+ document.body.style.background = "#eee";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+</head>
+
+<body onload="document.body.offsetHeight; setTimeout(b, 0);">
+
+<span class="dddd"><div></div><span id="float" style="float: left"></span></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-1.xhtml b/layout/base/crashtests/403569-1.xhtml
new file mode 100644
index 0000000000..c324597964
--- /dev/null
+++ b/layout/base/crashtests/403569-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+
+#a {
+ column-count: 2;
+ column-width: 100px;
+ float: left;
+ border: 2px solid magenta;
+ height: 200px;
+}
+
+#b {
+ column-count: 2;
+ column-width: 100px;
+ float: left;
+ border: 2px solid green;
+ height: 300px;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('span').style.display = '-moz-inline-box';">
+
+<div id="a"><div id="b"></div><optgroup label="foo"><span id="span"></span></optgroup></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-2.xhtml b/layout/base/crashtests/403569-2.xhtml
new file mode 100644
index 0000000000..2642729d0e
--- /dev/null
+++ b/layout/base/crashtests/403569-2.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+body {
+ column-count: 2;
+ column-width: 100px;
+ height: 200px;
+}
+#b {
+ float: left;
+ height: 300px;
+}
+</style>
+</head>
+<body onclick="document.getElementById('span').style.display = 'block';">
+<img src="../../../testing/crashtest/images/tree.gif" width="1070" height="335" id="b"/>
+<optgroup label="foo"><span id="span"></span></optgroup>
+</body>
+</html>
diff --git a/layout/base/crashtests/403569-3.xhtml b/layout/base/crashtests/403569-3.xhtml
new file mode 100644
index 0000000000..ddc4e40606
--- /dev/null
+++ b/layout/base/crashtests/403569-3.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+body {
+ column-count: 2;
+ column-width: 100px;
+ height: 200px;
+}
+#b {
+ float: left;
+ height: 300px;
+}
+
+.og:before {
+ display: block;
+ content: "foo";
+}
+
+</style>
+</head>
+<body onload="document.getElementById('span').style.display = 'block';">
+<img src="../../../testing/crashtest/images/tree.gif" width="1070" height="335" id="b"/>
+<div class="og"><span id="span"></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/404491-1.html b/layout/base/crashtests/404491-1.html
new file mode 100644
index 0000000000..540a0f6a1b
--- /dev/null
+++ b/layout/base/crashtests/404491-1.html
@@ -0,0 +1,5 @@
+<html>
+<body>
+<marquee><marquee></marquee><img></marquee>
+</body>
+</html>
diff --git a/layout/base/crashtests/404721-1.xhtml b/layout/base/crashtests/404721-1.xhtml
new file mode 100644
index 0000000000..ec9cbb07b8
--- /dev/null
+++ b/layout/base/crashtests/404721-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var s = document.getElementById("s");
+ s.parentNode.removeChild(s);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="column-width: 23px;"><div style="padding: 5px;"><span id="s"><div style="float: left;"><div style="width: 100px; height: 100px;"></div></div></span></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/404721-2.xhtml b/layout/base/crashtests/404721-2.xhtml
new file mode 100644
index 0000000000..4933e26c8b
--- /dev/null
+++ b/layout/base/crashtests/404721-2.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+ var s = document.getElementById("s");
+ s.parentNode.removeChild(s);
+}
+</script>
+</head>
+
+<body onload="boom();">
+
+<div style="column-width: 23px;"><div style="padding: 5px;"><div id="s"><td style="float: left;"><div style="width: 100px; height: 100px;"></div>
+ </td></div></div></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/405049-1.xhtml b/layout/base/crashtests/405049-1.xhtml
new file mode 100644
index 0000000000..07d2f4b18d
--- /dev/null
+++ b/layout/base/crashtests/405049-1.xhtml
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: table;">
+ <panel/>
+</window>
diff --git a/layout/base/crashtests/406675-1.html b/layout/base/crashtests/406675-1.html
new file mode 100644
index 0000000000..779d82b675
--- /dev/null
+++ b/layout/base/crashtests/406675-1.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var textNode = document.createTextNode("\u202B" + "A B");
+ document.body.appendChild(textNode);
+ document.body.offsetHeight;
+ textNode.data = "\u202B" + " C";
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/408292.html b/layout/base/crashtests/408292.html
new file mode 100644
index 0000000000..be8c642459
--- /dev/null
+++ b/layout/base/crashtests/408292.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style>
+#v {
+ column-count: 2;
+ width: 10ch;
+ height: 3.7em;
+ font: 14px monospace;
+ text-transform: lowercase;
+ direction: rtl;
+ border: 1px solid black;
+}
+</style>
+</head>
+<body onload="document.getElementById('v').style.direction = 'ltr';">
+<div id="v">aaaa bbbb cccc dddd eeee !</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/408299.html b/layout/base/crashtests/408299.html
new file mode 100644
index 0000000000..ecff93671b
--- /dev/null
+++ b/layout/base/crashtests/408299.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+
+<body style="direction: rtl; font-family: monospace;" onload="document.getElementById('v').style.width = '0';">
+
+<div id="v" style="column-count: 15; width: 1px; height: 2.7em; border: 1px solid black;">
+xxxxx yyyyy zzzzzz
+</div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/408450-1.xhtml b/layout/base/crashtests/408450-1.xhtml
new file mode 100644
index 0000000000..74a34ad915
--- /dev/null
+++ b/layout/base/crashtests/408450-1.xhtml
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+</head>
+<body>
+<div style="column-count: 15;"><div style="column-count: 15;"><td style="display: block; height: 2.5em;"><div style="height: 0.5em;"></div></td></div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/409461-1.xhtml b/layout/base/crashtests/409461-1.xhtml
new file mode 100644
index 0000000000..1abb421489
--- /dev/null
+++ b/layout/base/crashtests/409461-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+svg:after { content: 'generated'; }
+
+</style>
+</head>
+
+<body>
+
+<svg xmlns="http://www.w3.org/2000/svg" />
+
+</body>
+</html>
diff --git a/layout/base/crashtests/410967.html b/layout/base/crashtests/410967.html
new file mode 100644
index 0000000000..4895384f54
--- /dev/null
+++ b/layout/base/crashtests/410967.html
@@ -0,0 +1,17 @@
+<DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var t = document.body.firstChild;
+ t.data = "a" + t.data;
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="width: 1px;">b c&#1603;</body>
+
+</html>
diff --git a/layout/base/crashtests/411870-1.html b/layout/base/crashtests/411870-1.html
new file mode 100644
index 0000000000..a7ee883576
--- /dev/null
+++ b/layout/base/crashtests/411870-1.html
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<head>
+<script>
+
+function boom()
+{
+ document.body.appendChild(document.getElementById("v").firstChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();" style="white-space: pre; column-count: -1;"><div id="v"><span>
+</span>
+
+</div></body>
+
+</html>
diff --git a/layout/base/crashtests/412651-1-frame.xhtml b/layout/base/crashtests/412651-1-frame.xhtml
new file mode 100644
index 0000000000..80f10b544c
--- /dev/null
+++ b/layout/base/crashtests/412651-1-frame.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<head>
+
+<style type="text/css" id="s"></style>
+
+<script type="text/javascript">
+
+function lo()
+{
+ window.onerror = oe;
+ setTimeout(function(){ location.reload(); }, 200);
+}
+
+function oe(a,b,c)
+{
+ document.getElementById("s").textContent = ".nosuch { color: red }";
+}
+
+</script>
+
+</head>
+
+<body onload="lo();">
+ <xul:preference/>
+ <xul:tabs/>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/412651-1.html b/layout/base/crashtests/412651-1.html
new file mode 100644
index 0000000000..4640061ca7
--- /dev/null
+++ b/layout/base/crashtests/412651-1.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+var childLoads = 0;
+function inc()
+{
+ ++childLoads;
+ if (childLoads >= 2)
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body>
+
+<iframe src="412651-1-frame.xhtml" onload="inc();"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/413587-1.svg b/layout/base/crashtests/413587-1.svg
new file mode 100644
index 0000000000..7781d5ef95
--- /dev/null
+++ b/layout/base/crashtests/413587-1.svg
@@ -0,0 +1,11 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
+
+ <symbol id="foo">
+ <style type="text/css">
+ svg { counter-increment: x; }
+ </style>
+ </symbol>
+
+ <use xlink:href="#foo"/>
+
+</svg>
diff --git a/layout/base/crashtests/415503.xhtml b/layout/base/crashtests/415503.xhtml
new file mode 100644
index 0000000000..b2fcae89f6
--- /dev/null
+++ b/layout/base/crashtests/415503.xhtml
@@ -0,0 +1,28 @@
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<head>
+
+<style id="ss" type="text/css">
+
+span, popupgroup { display: table; position: absolute; }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var ss = document.getElementById("ss");
+ ss.removeChild(ss.firstChild);
+}
+
+</script>
+
+</head>
+
+<body onload="boom();">
+
+<span><xul:popupgroup/></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/416107.xhtml b/layout/base/crashtests/416107.xhtml
new file mode 100644
index 0000000000..753e48aca9
--- /dev/null
+++ b/layout/base/crashtests/416107.xhtml
@@ -0,0 +1,26 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var z = document.getElementById("z");
+ var c = document.getElementById("c");
+
+ z.removeChild(z.firstChild);
+ document.body.offsetHeight;
+ c.style.counterReset = "c";
+}
+
+</script>
+
+</head>
+
+<body onload="boom();" style="font-family: monospace; width: 7ch;">
+
+<span style="margin: 8px;"></span>
+
+<span style="position: relative;" id="z">OOO<span style="display: table-caption;">OOOOOO</span><span id="c" style="position: absolute;"></span></span>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/419985.html b/layout/base/crashtests/419985.html
new file mode 100644
index 0000000000..2f7360dfab
--- /dev/null
+++ b/layout/base/crashtests/419985.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+<head>
+<title>Crash [@ nsView::~nsView()] with onload focusing and removing window</title>
+</head>
+<body>
+<iframe id="content" onload="doe()" src="data:text/html;charset=utf-8,%3Chtml%3E%3Chead%3E%3C/head%3E%0A%3Cbody%20onfocus%3D%22window.frameElement.parentNode.removeChild%28window.frameElement%29%22%3E%0A%3Ciframe%20src%3D%22data%3Atext/html%3Bcharset%3Dutf-8%2C%253Cbody%2520onload%253D%2522document.links%255B0%255D.focus%2528%2529%253B%2522%253E%253Ca%2520href%253D%2522javascript%253A%2522%253Em%253C/a%253E%22%3E%3C/iframe%3E%0A%3Cstyle%20id%3D%22e%22%3E%0A@import%20URL%28416107.xhtml%29%3B%0A%3C/style%3E%0A%3C/body%3E%0A%3C/html%3E"></iframe>
+
+<script>
+// Run the test for 2 seconds
+setTimeout(function() {
+ clearInterval(i);
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+
+function doe2() {
+document.getElementById('content').src = document.getElementById('content').src;
+}
+var i = setInterval(doe2, 400);
+
+function doe(){
+document.getElementById('content').style.display = 'none';
+document.body.offsetHeight;
+document.getElementById('content').style.display = '';
+}
+</script>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/420031-1.html b/layout/base/crashtests/420031-1.html
new file mode 100644
index 0000000000..923174517f
--- /dev/null
+++ b/layout/base/crashtests/420031-1.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="var s = document.getElementById('s'); s.parentNode.removeChild(s);">
+<div style="height: 4em; column-count: 1;"><br><span id="s">foo<div style="float: right;">bar<div></div> baz</div></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/420213-1.html b/layout/base/crashtests/420213-1.html
new file mode 100644
index 0000000000..aa0db9fb6b
--- /dev/null
+++ b/layout/base/crashtests/420213-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<head>
+</head>
+<body onload="document.body.style.width = '5px';"><div style="column-width: 1px;">X<span style="height: 10px; float: right;"></span></div></body>
+</html>
diff --git a/layout/base/crashtests/420219-1.html b/layout/base/crashtests/420219-1.html
new file mode 100644
index 0000000000..6db7fd66ee
--- /dev/null
+++ b/layout/base/crashtests/420219-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("a").style.counterReset = "s";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);">
+
+<map name="m"><area id="a"></map>
+
+<img usemap="#m" src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B">
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/420651-1.xhtml b/layout/base/crashtests/420651-1.xhtml
new file mode 100644
index 0000000000..7896e3aaf1
--- /dev/null
+++ b/layout/base/crashtests/420651-1.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body style="column-count: 1; width: 10em; white-space: pre;">
+ <div style="padding: 12em; display: inline; white-space: normal;">
+ <input style="float: right;"></input></div>
+ </body></html>
diff --git a/layout/base/crashtests/421203-1.xhtml b/layout/base/crashtests/421203-1.xhtml
new file mode 100644
index 0000000000..f139997697
--- /dev/null
+++ b/layout/base/crashtests/421203-1.xhtml
@@ -0,0 +1,5 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<hbox flex="1" style="background: url(&quot;data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B&quot;); display: inline; direction: rtl;"/>
+
+</window>
diff --git a/layout/base/crashtests/421432.html b/layout/base/crashtests/421432.html
new file mode 100644
index 0000000000..37f8bff8f1
--- /dev/null
+++ b/layout/base/crashtests/421432.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>Crash [@ DocumentViewerImpl::LoadComplete] with focusing and removing iframe on reload</title>
+</head>
+<body>
+<iframe id="content" onload="window.frames[0].focus()" style="width:1000px;height: 300px;"></iframe>
+<script>
+function doe2() {
+document.getElementById('content').src = 'data:text/html;charset=utf-8,%3Cscript%3Ewindow.addEventListener%28%27focus%27%2C%20function%28e%29%20%7Bwindow.frameElement.parentNode.removeChild%28window.frameElement%29%3B%7D%2C%20true%29%3C/script%3E';
+}
+setInterval(doe2, 500);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/422276.html b/layout/base/crashtests/422276.html
new file mode 100644
index 0000000000..6d2a89b741
--- /dev/null
+++ b/layout/base/crashtests/422276.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+</head>
+<body>
+<div style="overflow: scroll;">
+ <div>
+ <q>
+ <span style="display: -moz-box;"></span>
+ </q>
+ </div>
+ <q></q>
+</div>
+
+<style>
+body *+* {quotes: "quote" "quote" !important;}
+</style>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/423107-1.xhtml b/layout/base/crashtests/423107-1.xhtml
new file mode 100644
index 0000000000..fcd06e4256
--- /dev/null
+++ b/layout/base/crashtests/423107-1.xhtml
@@ -0,0 +1,19 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+<script type="text/javascript" >
+
+function boom()
+{
+ var a = document.getElementById("a");
+ document.body.removeChild(a);
+ document.body.offsetHeight;
+ document.body.appendChild(a);
+}
+
+</script>
+</head>
+
+<body style="column-count: 3;" onload="boom();">1<div style="height: 1em;"></div><div id="a">2<select style="float: right;"></select></div></body>
+
+</html>
diff --git a/layout/base/crashtests/425981-1.html b/layout/base/crashtests/425981-1.html
new file mode 100644
index 0000000000..04413d225c
--- /dev/null
+++ b/layout/base/crashtests/425981-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+div:first-letter { float: left; }
+</style>
+<script>
+function boom()
+{
+ var v = document.getElementById("v");
+ var t = v.firstChild;
+ v.appendChild(document.createTextNode(" "));
+ v.removeChild(t);
+}
+</script>
+</head>
+<body onload="boom();"><div id="v" style="column-count: 2; width: 1px;">a b</div></body>
+</html>
diff --git a/layout/base/crashtests/428138-1.html b/layout/base/crashtests/428138-1.html
new file mode 100644
index 0000000000..470fc63022
--- /dev/null
+++ b/layout/base/crashtests/428138-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
+ var fo = document.createElementNS("http://www.w3.org/2000/svg", "foreignObject");
+ fo.style.padding = "10em";
+ svg.appendChild(fo);
+ document.body.appendChild(svg);
+ document.body.offsetHeight;
+
+ var opt = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ fo.appendChild(opt);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+
+</html>
diff --git a/layout/base/crashtests/428448-1.html b/layout/base/crashtests/428448-1.html
new file mode 100644
index 0000000000..033ca159a6
--- /dev/null
+++ b/layout/base/crashtests/428448-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+
+<body style="column-width: 1px"><span>!</span>
+<span style="float: left"></span>
+x</body>
+
+</html>
diff --git a/layout/base/crashtests/429088-1.html b/layout/base/crashtests/429088-1.html
new file mode 100644
index 0000000000..badda71d8e
--- /dev/null
+++ b/layout/base/crashtests/429088-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMSubtreeModified", function(){});
+
+ var MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+ var ms = document.createElementNS(MATHML_NS, "ms");
+ document.body.appendChild(ms);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/429088-2.html b/layout/base/crashtests/429088-2.html
new file mode 100644
index 0000000000..f56120ccb2
--- /dev/null
+++ b/layout/base/crashtests/429088-2.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ window.addEventListener("DOMSubtreeModified", function(){});
+
+ var span = document.createElement("span");
+ document.body.appendChild(span);
+}
+
+</script>
+
+<style type="text/css">
+
+span:before { content: '0'; }
+span:after { content: '1'; }
+
+</style>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/429865-1.html b/layout/base/crashtests/429865-1.html
new file mode 100644
index 0000000000..18b7bcbfb9
--- /dev/null
+++ b/layout/base/crashtests/429865-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+body:after { content: '0'; }
+body:first-letter { float: right; }
+
+</style>
+</head>
+
+<body> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/429881.html b/layout/base/crashtests/429881.html
new file mode 100644
index 0000000000..78d2a8ce15
--- /dev/null
+++ b/layout/base/crashtests/429881.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="position: relative; column-width: 5em;" onload="document.body.removeChild(document.body.firstChild)"><div id="d"><div style="white-space: pre; position: absolute;">
+B<div style="position: fixed;"></div></div></div></body>
+</html>
diff --git a/layout/base/crashtests/430569-1.html b/layout/base/crashtests/430569-1.html
new file mode 100644
index 0000000000..b05b708b1f
--- /dev/null
+++ b/layout/base/crashtests/430569-1.html
@@ -0,0 +1,3 @@
+<!DOCTYPE html>
+<html style="height: 12px; white-space: pre;"><body style="position: fixed; height: inherit; column-width: 1px;" onload="document.documentElement.style.height = '';"><div></div>
+</body></html>
diff --git a/layout/base/crashtests/430569-2.html b/layout/base/crashtests/430569-2.html
new file mode 100644
index 0000000000..432353c03c
--- /dev/null
+++ b/layout/base/crashtests/430569-2.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<!-- This height has to be less than 18px to trigger crash, on Linux. -->
+<html style="height: 10px;
+ background: lightblue"
+ ><body style="position: fixed;
+ height: inherit;
+ column-count: 1;
+ background: yellow;
+ width: 100px"
+ onload="document.documentElement.style.height = ''"
+ ><div style="outline: 1px dotted green"></div><br/></body></html>
diff --git a/layout/base/crashtests/432752-1.svg b/layout/base/crashtests/432752-1.svg
new file mode 100644
index 0000000000..f5ea2aeb92
--- /dev/null
+++ b/layout/base/crashtests/432752-1.svg
@@ -0,0 +1,27 @@
+<svg xmlns:xlink="http://www.w3.org/1999/xlink"
+ xmlns="http://www.w3.org/2000/svg"
+ onload="boom();">
+
+ <script type="text/javascript">
+
+ function boom()
+ {
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ var d = document.getElementById("d");
+
+ d.appendChild(b);
+ a.appendChild(document.createTextNode("A"));
+ }
+
+ </script>
+
+ <g id="a"></g>
+
+ <use xlink:href="#a" id="b"/>
+
+ <use xlink:href="#d">
+ <g id="d"/>
+ </use>
+
+</svg>
diff --git a/layout/base/crashtests/433450-1.html b/layout/base/crashtests/433450-1.html
new file mode 100644
index 0000000000..9215677c4c
--- /dev/null
+++ b/layout/base/crashtests/433450-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3c.org/TR/1999/REC-html401-19991224/loose.dtd">
+
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("v").style.border = "1px solid blue";
+ document.getElementById("li").style.padding = "5px 7px";
+}
+
+</script>
+
+</head>
+
+<body onload="boom();"><div style="column-count: 2;"><div style="margin-bottom: 5px;" id="v"></div><li id="li" style="border: 1px solid green;">
+<span style="border: 1px solid red; float: left;"></span></li></div></body>
+</html>
diff --git a/layout/base/crashtests/436982-1.html b/layout/base/crashtests/436982-1.html
new file mode 100644
index 0000000000..425961b1a7
--- /dev/null
+++ b/layout/base/crashtests/436982-1.html
@@ -0,0 +1,7 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><head>
+<style>
+
+div:first-letter { float: right; }
+
+</style>
+</head><body onload="document.getElementById('a').style.fontFamily = 'a';"><div id="a"> &#x202B;<span></span></div></body></html>
diff --git a/layout/base/crashtests/437142-1.html b/layout/base/crashtests/437142-1.html
new file mode 100644
index 0000000000..6500e9fbe7
--- /dev/null
+++ b/layout/base/crashtests/437142-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var m = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mrow");
+ document.body.appendChild(m);
+ document.getElementById("a").style.display = "inline";
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);">
+
+<img usemap="#Map" src="data:image/gif,GIF87a%02%00%02%00%B3%00%00%00%00%00%FF%FF%FF%00%00%00%00%00%00%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%2C%00%00%00%00%02%00%02%00%00%04%03%90H%12%00%3B">
+
+<map name="Map"><area id="a"></map>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/439258-1.html b/layout/base/crashtests/439258-1.html
new file mode 100644
index 0000000000..87b6f98d78
--- /dev/null
+++ b/layout/base/crashtests/439258-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("div").focus();
+ document.execCommand("bold", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();">
+
+<div id="div" style="position: absolute" contenteditable="true"></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/439343.html b/layout/base/crashtests/439343.html
new file mode 100644
index 0000000000..9537c9e5fb
--- /dev/null
+++ b/layout/base/crashtests/439343.html
@@ -0,0 +1,2 @@
+<textarea style="text-shadow: black 0px 0px 5px;text-indent: -9999999999999999px;font-size: 900;letter-spacing: 10em;">
+mmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmmmmmmmmm mmmmmmmmmmmmmmmmmmmm \ No newline at end of file
diff --git a/layout/base/crashtests/444863-1.html b/layout/base/crashtests/444863-1.html
new file mode 100644
index 0000000000..3a1bc206b8
--- /dev/null
+++ b/layout/base/crashtests/444863-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+#a {
+ float: left;
+ position: relative;
+ padding: 331890106943cm 0;
+}
+
+#b {
+ position: absolute;
+ top: 100%;
+}
+
+
+</style>
+</head>
+
+<body>
+<div id="a"><div id="b"></div></div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/444925-1.xhtml b/layout/base/crashtests/444925-1.xhtml
new file mode 100644
index 0000000000..adf5603b90
--- /dev/null
+++ b/layout/base/crashtests/444925-1.xhtml
@@ -0,0 +1,10 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:mathml="http://www.w3.org/1998/Math/MathML">
+<box>
+m
+<mathml:munderover style="padding-left: 20%; padding-right: 50%; text-shadow: 0px 0px 3.5px orange; text-decoration: overline;">
+m
+<box/>
+<box/>
+</mathml:munderover>
+</box>
+</window>
diff --git a/layout/base/crashtests/444967-1.html b/layout/base/crashtests/444967-1.html
new file mode 100644
index 0000000000..a45ee59416
--- /dev/null
+++ b/layout/base/crashtests/444967-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+body {
+ margin: 0pt; direction: rtl; word-spacing: 68710545em;
+ background: url("data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%0A(%00%00%00(%08%02%00%00%00%E13w%BE%00%00%02'iCCPICC%20Profile%00%00x%9C%95%92%BFk%13a%18%C7%3F%97PZ%D4%86R%A3%88%22%DE%20%D6B%D43%E9%901m~H%92%23%9EIJ%9A%D0%25%B9K%93hr9.%97%F8%03%85n%AEN%BAfQ2tT%EA%24%01%17%3B%14%2C%15%5B%FC%0B%BAW%BAH%8D%C3y%17%10J%F1%81%17%3E%CF%C3%F3%3E%3F%BE%EF%0B%DE%9Fe%C3hz%80%96n%99%D9%7BK%E2J%B1%24N%EE3%C15%CEp%81%F9%B2%DA1%16%15E%E6D%3B%FA%8E%00%B0%7B%ABl%18%CD%99%D5W%5B%2FS%07%5B%1F~%BF%D9%BD%3Aw9p%F2%3D%00%7C%E6J%B1%04B%00%F0%D7l%8E%00%FE%8A%CDy%C0%FF%D82%2C%10%EA%80_%AD%975%10%9E%03%013%9F%8D%820%00%7C5%9B%3F%02%BE%8A%CD_%00_O%ADY%20%EC%03%92%AE5t%F0L%01a%AD%DAQ%C1%13%01jZGm%81g%00%02%ADV%5B%03%EF%00%B8%A9%1A%A6%05%DE%AF%C0%F5%95bI%B4G%EE%CB%90%DE%01%BE%8Dcw%A6%A1%FF%16%FC%A9ql%EE%05%9C%EF%C3%A7%A9q%ECp%1F%01%10.Nu%D6BA%00%84%B3%9B0%F1z4%3A%2C%C0%E4%3C%1C%EF%8DF%BF6G%A3%E3w%E0%FD%01%C3%23%B5k%F6%FE%EA%25%08%DBp%9Ao%EFl%FB%B0c9t%12%DB%BA%00%20%C1%C6%3A%3C%18%82%04%BC%1F%C2%8D%3D%98%BD%02J%04%F2%11%3C%A1%90sl%0D%01%98%8E%C5eY%0C.H%E1BB9%E5%CD%FF%DBZ%CD%AE%D3g%168%A7W2%F7%81K%C0%81a)y%87%3B%BD%5C%DC%E1%B5F%22%E9%B0V%8E%A5%1D~V%8Ff%DC%1C3%91u%F8a9%A58%5C%D5%97sn%FD%A6%22%8F%7B-%B9%F9%D5N%3C7%AE%99%2F8lv%B3%CB%0E%3Fj%A7%DD%7C%AD%1Asg%D3%9B%19%B7f%C3J%BA%F3%13%23%8E%8C%8CH%90%05%24%C2%14H%A0%D8%7F%12%60f%03%FA%F3w%A5P%EE%F3va%FD_%9D%AC%EA%13%0B%20%DA6%9E%9A%8DZ%DD%12%17%0D%A3Y%0D%88I%5D%BD%1D%10%83%92%14%E6%0Fy4%B1%B9%90%3E%3B%0B%00%00%02%FBIDATx%9C%ED%D9%3Br%DB%40%14E%C1%A1%F7%BFg%3Aq%E0R%D9%FA%10%001g%A6%3BR%22%88%AC%BAO%01%CEc%AC%E8%F1x%BC%FC%C3%91%DF%9D%E1%F9%CB%7C%11%CF_%FB%F9%CB%7C%11%CF%F7%FC%C4%1F%F2%FCi%9F%0F%00%00%00%00%B0%8C_w%7F%80%ED%3C%9F%CF%BB%3F%02%7C%CDPa8%04%00%00%00%00%00%F86%E1%F9%23%99%81%04C%85%E1%10%880T%00%00%00%00%60%07%C23%5CBf%80%E1%10%880T%00%00%00%00%80%E3%84g6%253%C0p%08D%18*%00%00%00%00%C0%FC%84g%80y%E9m%24%18*%00%00%00%00%00%C23%93%921H0T%12%0C%15%00%00%00%00%80%AB%09%CF%BCH%C6%20%C1PI0T%00%00%00%00%00%EA%84%E7e%C9%18%24%18*%09%86%0A%00%00%00%00%00%9F%13%9Eo%23c%90%60%A8%24%18*%09%86%0A%00%00%00%00%2CLx%FE%2Fo%87I0T%12%0C%95%04C%05%00%00%00%00x%99%F0%0C%D7%921H0T%12%0C%15%00%00%00%00%60Z%C23%BB%931H0T%12%0C%15%00%00%00%00%60%5B%C23%B3%931H0T%12%0C%15%00%00%00%00%80%8B%08%CF%1C%25c%90%60%A8%24%18*%00%00%00%00%00Q%C2%F3%FAd%0C%12%0C%95%04C%05%00%00%00%00%80%7F%12%9E%EF'c%90%60%A8%24%18*%09%86%0A%00%00%00%00%ACGx%FE%9A%B7%C3%24%18*%09%86%0A%00%00%00%00%00K%12%9E%E1M%F46%12%0C%15%86C%00%00%00%00%00%F89%E1%19%FE%90%19H0T%18%0E%01%00%00%00%00%60%3E%C23%192%03%09%86%0A%C3!%00%00%00%00%00%ECGx%E642%03%40%85%FF%D8%00%00%00%00%00%9CKx%DE%88%CC%00%C3!%10a%A8%00%00%00%00%00%B4%08%CF%13%91%19%608%04%22%0C%15%00%00%00%00%00%FE%26%3C%FF%80%CC%00%C3!%10a%A8%00%00%00%00%00%F0N%C23%ACFo%23%C1PI0T%00%00%00%00%80o%12%9E%E1%DDd%0C%12%0C%95%04C%05%00%00%00%00%98%84%F0%0C%1F%C9%18%24%18*%09%86%0A%00%00%00%00%B0%09%E1%99%1E%19%83%04C%25%C1P%01%00%00%00%008%85%F0%CC%F9d%0C%12%0C%95%04C%05%00%00%00%00%20Ax%DE%91%8CA%82%A1%92%60%A8%00%00%00%00%000%84%E79%C9%18%24%18*%09%86%0A%00%00%00%00%00o%20%3C%BFB%C6%20%C1PI0T%12%0C%15%00%00%00%00%E0sk%86go%87I0T%12%0C%95%04C%05%00%00%00%00%B8%D7%9A%E1%19N!c%90%60%A8%24%18*%00%00%00%00%C0%DA%84g%C2d%0C%12%0C%95%04C%05%00%00%00%00%E0%08%E1%99%0B%C9%18%24%18*%09%86%0A%00%00%00%00%C0%CC%84%E7%AD%C9%18%24%18*%00%00%00%00%00%C0%E4%84%E7%A9%E9m%24%18*%0C%87%00%00%00%00%00%C0%DE%84%E7Cd%06%12%0C%15%86C%00%00%00%00%00%80%2B%FD%06%ECHi%F4%A12%1A%B2%00%00%00%00IEND%AEB%60%82");
+}
+</style>
+</head>
+<body>A B </body>
+</html>
diff --git a/layout/base/crashtests/446328-iframe.html b/layout/base/crashtests/446328-iframe.html
new file mode 100644
index 0000000000..b67942b890
--- /dev/null
+++ b/layout/base/crashtests/446328-iframe.html
@@ -0,0 +1 @@
+<html><head></head><body style='border-image: url(446328.gif) 2 3 1 1 / 50px 50px'></body></html>
diff --git a/layout/base/crashtests/446328-top.html b/layout/base/crashtests/446328-top.html
new file mode 100644
index 0000000000..9a184833ac
--- /dev/null
+++ b/layout/base/crashtests/446328-top.html
@@ -0,0 +1,21 @@
+<html>
+<head>
+<title>Bug 446328 – Crash [@ nsImageLoader::RedrawDirtyFrame] with document that has border-image and gets display: none</title>
+</head>
+<body>
+<iframe src="446328-iframe.html" id="a"></iframe>
+<script>
+function doe(){
+ var v = document.body.offsetHeight;
+ document.getElementById('a').style.display = 'none';
+ document.getElementById('a').style.display = 'inline';
+ v = document.body.offsetHeight;
+ document.getElementById('a').style.display = 'block';
+ document.getElementById('a').style.display = 'none';
+ v = document.body.offsetHeight;
+}
+setTimeout(doe,10);
+setTimeout("location.reload()",20);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/446328.gif b/layout/base/crashtests/446328.gif
new file mode 100644
index 0000000000..9c5dd937fe
--- /dev/null
+++ b/layout/base/crashtests/446328.gif
Binary files differ
diff --git a/layout/base/crashtests/446328.html b/layout/base/crashtests/446328.html
new file mode 100644
index 0000000000..20061761b3
--- /dev/null
+++ b/layout/base/crashtests/446328.html
@@ -0,0 +1,12 @@
+<html class="reftest-wait">
+<head>
+<title>Bug 446328 – Crash [@ nsImageLoader::RedrawDirtyFrame] with document that has border-image and gets display: none</title>
+</head>
+<body>
+<iframe src="446328-top.html" id="a"></iframe>
+<script>
+function doe(){
+ document.documentElement.removeAttribute("class");
+}
+setTimeout(doe,700)
+</script>
diff --git a/layout/base/crashtests/448488-1.html b/layout/base/crashtests/448488-1.html
new file mode 100644
index 0000000000..d985cc32ee
--- /dev/null
+++ b/layout/base/crashtests/448488-1.html
@@ -0,0 +1,4 @@
+<html>
+<head></head>
+<body style="width: 17179869184ch"></body>
+</html>
diff --git a/layout/base/crashtests/448543-1.html b/layout/base/crashtests/448543-1.html
new file mode 100644
index 0000000000..e44bcd4e24
--- /dev/null
+++ b/layout/base/crashtests/448543-1.html
@@ -0,0 +1,8 @@
+<html><head>
+</head>
+<body style="display: table;">
+
+<iframe></iframe>
+<video style="display: table-column-group;"></video>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/448543-2.html b/layout/base/crashtests/448543-2.html
new file mode 100644
index 0000000000..9307b0c24b
--- /dev/null
+++ b/layout/base/crashtests/448543-2.html
@@ -0,0 +1 @@
+<strike style="display: table-header-group;"><video style="display: table-row;"> \ No newline at end of file
diff --git a/layout/base/crashtests/448543-3.html b/layout/base/crashtests/448543-3.html
new file mode 100644
index 0000000000..7ae8a170d4
--- /dev/null
+++ b/layout/base/crashtests/448543-3.html
@@ -0,0 +1,7 @@
+<html><head>
+</head><body>
+<div style="display: table-row-group;">
+<iframe style="float: left;"></iframe>
+<video style="display: table;"></video>
+</div>
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/450319-1.xhtml b/layout/base/crashtests/450319-1.xhtml
new file mode 100644
index 0000000000..c073593c86
--- /dev/null
+++ b/layout/base/crashtests/450319-1.xhtml
@@ -0,0 +1,32 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" class="reftest-wait">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var newSpan = document.createElement('span');
+ var mr = document.getElementById("mr");
+ mr.appendChild(newSpan);
+
+ var vv = document.getElementById("vv");
+ vv.parentNode.removeChild(vv);
+
+ document.documentElement.removeAttribute("class");
+}
+
+</script>
+
+<style type="text/css">
+
+body { background: url("data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B"); }
+
+</style>
+</head>
+
+<div></div>
+
+<body onload="setTimeout(boom, 0);"><iframe /><m:mtd /><m:mrow id="mr" /></body>
+
+<span><div id="vv"></div></span>
+
+</html>
diff --git a/layout/base/crashtests/453894-1.xhtml b/layout/base/crashtests/453894-1.xhtml
new file mode 100644
index 0000000000..7d8bd9557c
--- /dev/null
+++ b/layout/base/crashtests/453894-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;">
+<head>
+<style type="text/css">
+
+.bg { background: url("data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B"); }
+
+</style>
+</head>
+
+<body>
+
+<table style="letter-spacing: 1300000px;"><tbody style="font-size: 9%;" class="bg"><tr><td>BBBBBBBBBBBBB BBBBBBBBBBBBB BBBBBBBBBBBBB BBBBBBBBBBBBB</td>X</tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/454751-1.xhtml b/layout/base/crashtests/454751-1.xhtml
new file mode 100644
index 0000000000..51d01a8778
--- /dev/null
+++ b/layout/base/crashtests/454751-1.xhtml
@@ -0,0 +1,20 @@
+<?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" onload="addStyleSheet('window { display: table; }');">
+
+<script type="text/javascript">
+
+function addStyleSheet(text)
+{
+ var sheet = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ sheet.appendChild(document.createTextNode(text));
+ document.documentElement.appendChild(sheet);
+}
+
+</script>
+
+<treecols/>
+
+</window>
diff --git a/layout/base/crashtests/455063-1.html b/layout/base/crashtests/455063-1.html
new file mode 100644
index 0000000000..bb8e5e9dd2
--- /dev/null
+++ b/layout/base/crashtests/455063-1.html
@@ -0,0 +1,6 @@
+<html>
+<body onload="document.documentElement.style.display = 'table'">
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455063-2.html b/layout/base/crashtests/455063-2.html
new file mode 100644
index 0000000000..666998eda5
--- /dev/null
+++ b/layout/base/crashtests/455063-2.html
@@ -0,0 +1,6 @@
+<html style="display:table">
+<body onload="document.documentElement.style.display = 'block'">
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455063-3.html b/layout/base/crashtests/455063-3.html
new file mode 100644
index 0000000000..f0054ea08a
--- /dev/null
+++ b/layout/base/crashtests/455063-3.html
@@ -0,0 +1,6 @@
+<html style="display:block;column-count:2;">
+<body>
+ <span><div></div></span>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/455171-4.html b/layout/base/crashtests/455171-4.html
new file mode 100644
index 0000000000..67e7b8135f
--- /dev/null
+++ b/layout/base/crashtests/455171-4.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<title>Testcase, bug 455171</title>
+<div style="transform: translate(50px, 50px);"><div id="foo" style="position: fixed;"></div></div>
+<script type="text/javascript">
+var foo = document.getElementById("foo");
+var h = foo.offsetHeight;
+foo.remove();
+</script>
diff --git a/layout/base/crashtests/455623-1.html b/layout/base/crashtests/455623-1.html
new file mode 100644
index 0000000000..3a363005b6
--- /dev/null
+++ b/layout/base/crashtests/455623-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var i = document.getElementById("i");
+ i.contentDocument.designMode = "on";
+ i.previousSibling.data += "x\n";
+ i.style.counterReset = "c";
+}
+
+</script>
+</head>
+
+<body onload="boom();">&#x202E;<iframe id="i" src="data:text/html,a"></body>
+
+</html>
diff --git a/layout/base/crashtests/457362-1.xhtml b/layout/base/crashtests/457362-1.xhtml
new file mode 100644
index 0000000000..f5e7f0c82c
--- /dev/null
+++ b/layout/base/crashtests/457362-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="filter: url('#b');">
+
+<head></head>
+
+<body onload="document.getElementById('a').style.position = 'relative';">
+<hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" style="display: block; float: left;" id="a"><treecols id="b"/></hbox>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/457514.html b/layout/base/crashtests/457514.html
new file mode 100644
index 0000000000..6bf3e0b54b
--- /dev/null
+++ b/layout/base/crashtests/457514.html
@@ -0,0 +1,27 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var d = document.getElementById("d");
+ var s = document.getElementById("s");
+ s.insertBefore(document.createTextNode("T"), s.firstChild);
+ d.appendChild(s);
+ s.appendChild(document.createTextNode("\n"));
+}
+
+</script>
+
+<style type="text/css">
+
+div::first-letter { float: left; }
+
+</style>
+
+</head>
+
+<body onload="boom();"><div id="d"><span id="s">h</span></div></body>
+
+</html>
diff --git a/layout/base/crashtests/460389-1.html b/layout/base/crashtests/460389-1.html
new file mode 100644
index 0000000000..c6e2e04cc8
--- /dev/null
+++ b/layout/base/crashtests/460389-1.html
@@ -0,0 +1,6 @@
+<html>
+<head><style id="s">div:first-letter { float: left; }</style></head>
+<body onload="document.getElementById('s').disabled = true;">
+<div style="column-count: 2;"> &#x06CD;<div>T</div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/46043-1.html b/layout/base/crashtests/46043-1.html
new file mode 100644
index 0000000000..cd394a0180
--- /dev/null
+++ b/layout/base/crashtests/46043-1.html
@@ -0,0 +1,12 @@
+<html><head><title>Testcase for bug 46043</title></head>
+<body>
+
+<div style="float:right;width:600;background:blue">&nbsp;</div>
+<div style="float:right;width:400;background:yellow">&nbsp;</div>
+
+<ol>
+ <li>foo</li>
+</ol>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/462392.html b/layout/base/crashtests/462392.html
new file mode 100644
index 0000000000..327d1c6928
--- /dev/null
+++ b/layout/base/crashtests/462392.html
@@ -0,0 +1,43 @@
+<html>
+<head>
+ <title>Crash</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ <meta http-equiv="Content-Style-Type" content="text/css">
+ <meta http-equiv="Content-Script-Type" content="text/javascript">
+ <style type="text/css">
+
+ </style>
+ <script type="text/javascript">
+
+ function run() {
+ var ifr = document.getElementById("if");
+ var cd = ifr.contentDocument;
+ cd.open();
+ cd.write("<body onresize='parent.setup(); location.reload()'>");
+ cd.close();
+
+ // resize the child
+ ifr.style.width = "500px";
+ }
+
+ function setup() {
+ var ifr = document.getElementById("if");
+ var cd = ifr.contentDocument;
+
+ // put a pending repaint on the child
+ cd.body.style.backgroundColor = 'green';
+
+ // put a pending reframe that destroys the frame on the parent
+ ifr.style.display = 'none';
+
+ // Let the location.reload() call RebuildAllStyleData.
+ }
+
+ </script>
+</head>
+<body onload="run()">
+
+<iframe id="if"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/466763-1.html b/layout/base/crashtests/466763-1.html
new file mode 100644
index 0000000000..406720a323
--- /dev/null
+++ b/layout/base/crashtests/466763-1.html
@@ -0,0 +1,24 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var s = document.getElementById("s");
+ s.appendChild(s.previousSibling);
+ s.parentNode.removeAttribute("class");
+ document.documentElement.offsetHeight;
+ s.appendChild(document.createTextNode(" "));
+}
+
+</script>
+<style type="text/css">
+
+.flfr:first-letter { float: right; }
+
+</style>
+</head>
+
+<body onload="boom();" class="flfr">&#xFEB7; <span id="s"></span></body>
+
+</html>
diff --git a/layout/base/crashtests/467881-1.html b/layout/base/crashtests/467881-1.html
new file mode 100644
index 0000000000..623a26d166
--- /dev/null
+++ b/layout/base/crashtests/467881-1.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var HTML_NS = "http://www.w3.org/1999/xhtml";
+ var outer, inner, q;
+
+ function a()
+ {
+ outer = document.createElementNS(HTML_NS, "div");
+ inner = document.createElementNS(HTML_NS, "div");
+
+ inner.appendChild(document.createElementNS(HTML_NS, "iframe"));
+ inner.appendChild(document.createElementNS(HTML_NS, "div"));
+ inner.appendChild(q = document.createElementNS(HTML_NS, "span"));
+
+ outer.appendChild(inner);
+ document.documentElement.appendChild(outer);
+ setTimeout(b, 10);
+ }
+
+ function b()
+ {
+ outer.appendChild(document.createElementNS(HTML_NS, "span"));
+ setTimeout(c, 10);
+ }
+
+ function c()
+ {
+ q.appendChild(document.createElementNS(HTML_NS, "div"));
+ document.documentElement.removeAttribute("class");
+ }
+
+ a();
+}
+
+window.addEventListener("load", boom);
+
+</script>
+</head>
+
+<frameset></frameset>
+
+</html>
diff --git a/layout/base/crashtests/468491-1.html b/layout/base/crashtests/468491-1.html
new file mode 100644
index 0000000000..bff8574836
--- /dev/null
+++ b/layout/base/crashtests/468491-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<style>
+body::first-letter { float: left; }
+</style>
+</head>
+<body style="width:0">
+mm mm mm mm mm mm mm mm mm mm mm mm mm mm mm
+<span id="a"></span>
+<script>
+document.body.offsetWidth;
+document.getElementById('a').setAttribute('style', 'display: block;');
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/468555-1.xhtml b/layout/base/crashtests/468555-1.xhtml
new file mode 100644
index 0000000000..875bb098a1
--- /dev/null
+++ b/layout/base/crashtests/468555-1.xhtml
@@ -0,0 +1,9 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+* { content: 'Y'; }
+table:after { content: inherit; }
+</style>
+</head>
+<body onload="document.getElementById('t').appendChild(document.createElement('span'));"><table id="t"></table></body>
+</html>
diff --git a/layout/base/crashtests/468563-1.html b/layout/base/crashtests/468563-1.html
new file mode 100644
index 0000000000..e30b6d20bc
--- /dev/null
+++ b/layout/base/crashtests/468563-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body onload="document.getElementById('d').style.columnWidth = '';">
+<div id="d" style="height: 1px; column-width: 1px;">d d <span style="position: absolute;"></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/468578-1.xhtml b/layout/base/crashtests/468578-1.xhtml
new file mode 100644
index 0000000000..6668ac5bf6
--- /dev/null
+++ b/layout/base/crashtests/468578-1.xhtml
@@ -0,0 +1,21 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+// <![CDATA[
+
+function boom()
+{
+ var legend = document.getElementById("legend");
+ legend.appendChild(document.createTextNode("T"));
+ document.documentElement.offsetHeight;
+ legend.firstChild.remove();
+ document.body.removeChild(legend);
+}
+
+// ]]>
+</script>
+</head>
+
+<body onload="boom();" style="column-width: 0pc;"><legend id="legend" style="white-space: pre-line; padding-bottom: 90px; display: block;">
+ </legend></body>
+</html>
diff --git a/layout/base/crashtests/468645-3.xhtml b/layout/base/crashtests/468645-3.xhtml
new file mode 100644
index 0000000000..8e8dd8d71b
--- /dev/null
+++ b/layout/base/crashtests/468645-3.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:html="http://www.w3.org/1999/xhtml" class="reftest-paged">
+<menupopup xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/>
+
+<style>html::before, window::before { content:""; display: table; position: fixed;}</style>
+</html>
diff --git a/layout/base/crashtests/469861-1.xhtml b/layout/base/crashtests/469861-1.xhtml
new file mode 100644
index 0000000000..6a7888b62f
--- /dev/null
+++ b/layout/base/crashtests/469861-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" >
+<head>
+<style type="text/css">
+
+math, mtable { position: fixed; }
+math { display: inline-table; }
+
+</style>
+</head>
+<body>
+
+<m:math><m:mtable></m:mtable></m:math>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/469861-2.xhtml b/layout/base/crashtests/469861-2.xhtml
new file mode 100644
index 0000000000..295f2c61d4
--- /dev/null
+++ b/layout/base/crashtests/469861-2.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML" >
+<head>
+<style type="text/css">
+
+math, mtable { position: fixed; }
+math { display: table; }
+
+</style>
+</head>
+<body>
+
+<m:math><m:mtable></m:mtable></m:math>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/470851-1.xhtml b/layout/base/crashtests/470851-1.xhtml
new file mode 100644
index 0000000000..6fad2e9538
--- /dev/null
+++ b/layout/base/crashtests/470851-1.xhtml
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-paged">
+<thead>
+ <tbody style="top: 10%;position: relative;overflow: scroll;">
+ <td>
+ <tfoot>
+ <tr style="page-break-after: left;"></tr>
+ <tbody style="line-height: 999px;">m</tbody>
+ </tfoot>
+ <thead></thead>
+ </td>
+ </tbody>
+</thead>
+</html>
diff --git a/layout/base/crashtests/473042.xhtml b/layout/base/crashtests/473042.xhtml
new file mode 100644
index 0000000000..a92b2481ff
--- /dev/null
+++ b/layout/base/crashtests/473042.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="display: none;"><mrow xmlns="http://www.w3.org/1998/Math/MathML"></html> \ No newline at end of file
diff --git a/layout/base/crashtests/474075.html b/layout/base/crashtests/474075.html
new file mode 100644
index 0000000000..d5a7267d87
--- /dev/null
+++ b/layout/base/crashtests/474075.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+
+<body onload="document.getElementById('a').style.fontWeight = 'bold';document.documentElement.offsetHeight;">
+
+<div style="top: -2px; bottom: -8px; position: fixed; column-count: 1;"><div id="a" style="float: right; padding: 800px;"></div><div><div style="clear: right;"><div style="font-size-adjust: 1073741823; white-space: pre;">
+<input style="position: fixed;"></div></div></div></div>
+
+</body>
+
+</html>
diff --git a/layout/base/crashtests/477333-1.xhtml b/layout/base/crashtests/477333-1.xhtml
new file mode 100644
index 0000000000..037dfa1887
--- /dev/null
+++ b/layout/base/crashtests/477333-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="overflow-x: scroll">
+<head>
+<style type="text/css">
+
+body:first-letter { }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ td = document.createElement("td");
+ td.contentEditable = "true";
+ document.body.appendChild(td);
+ document.execCommand("strikethrough", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/477731-1.html b/layout/base/crashtests/477731-1.html
new file mode 100644
index 0000000000..99b02b8657
--- /dev/null
+++ b/layout/base/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/base/crashtests/47843-1.html b/layout/base/crashtests/47843-1.html
new file mode 100644
index 0000000000..f8ce8b08d8
--- /dev/null
+++ b/layout/base/crashtests/47843-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+ <head>
+ <title>Testcase for bug 47843</title>
+ <style type="text/css">
+ BODY {overflow:scroll;}
+ </style>
+ </head>
+ <body>
+ <P>Blah
+ </body>
+</html>
+
diff --git a/layout/base/crashtests/479114-1.html b/layout/base/crashtests/479114-1.html
new file mode 100644
index 0000000000..b60d5e8319
--- /dev/null
+++ b/layout/base/crashtests/479114-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+ <body>
+ <div style="display: table-row">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ <div style="display: table-row-group">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ <div style="display: table">
+ <span style="display: block; page-break-before: always"></span>
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/479360-1.xhtml b/layout/base/crashtests/479360-1.xhtml
new file mode 100644
index 0000000000..221d1c4a6c
--- /dev/null
+++ b/layout/base/crashtests/479360-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.style.display = "none";
+ document.execCommand("removeformat", false, null);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><td contenteditable="true"></td></body>
+
+</html>
diff --git a/layout/base/crashtests/480686-1.html b/layout/base/crashtests/480686-1.html
new file mode 100644
index 0000000000..bfaba06d1b
--- /dev/null
+++ b/layout/base/crashtests/480686-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+span { background: url(../../../testing/crashtest/images/tree.gif); }
+
+</style>
+</head>
+
+<body><div style="direction: rtl;"><div style="column-width: 1px;"><span>Q<input></span></div></div></body>
+
+</html>
diff --git a/layout/base/crashtests/481806-1.html b/layout/base/crashtests/481806-1.html
new file mode 100644
index 0000000000..9b12557ee6
--- /dev/null
+++ b/layout/base/crashtests/481806-1.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom()
+{
+ var hbox = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "hbox");
+ document.removeChild(document.documentElement);
+ document.appendChild(hbox);
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/483604-1.xhtml b/layout/base/crashtests/483604-1.xhtml
new file mode 100644
index 0000000000..2856951ca9
--- /dev/null
+++ b/layout/base/crashtests/483604-1.xhtml
@@ -0,0 +1,6 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<style>colgroup::before { content:"b";}</style>
+<colgroup/>
+
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/485501-1.html b/layout/base/crashtests/485501-1.html
new file mode 100644
index 0000000000..67cbcb9b41
--- /dev/null
+++ b/layout/base/crashtests/485501-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE HTML>
+<div style="overflow: hidden; border-radius: 50px; height: 200px; width: 200px; background:yellow">
+ This is some text that should get clipped at the corners by the border radius.
+</div>
diff --git a/layout/base/crashtests/488390-1.xhtml b/layout/base/crashtests/488390-1.xhtml
new file mode 100644
index 0000000000..ef7568b594
--- /dev/null
+++ b/layout/base/crashtests/488390-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("select").removeAttribute("multiple");
+ // Trigger a reflow
+ document.body.style.width = 0;
+ // And flush layout
+ document.body.offsetWidth;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span>&#x202E;text text text text text&#x202C;<span><dir/></span><span><tr><select id="select" multiple="multiple"/></tr></span></span></body>
+</html>
diff --git a/layout/base/crashtests/489691.html b/layout/base/crashtests/489691.html
new file mode 100644
index 0000000000..b9c1656366
--- /dev/null
+++ b/layout/base/crashtests/489691.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style type="text/css">
+
+span:first-letter { }
+
+</style>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("q").style.direction = "";
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="q" style="direction: rtl; display: block;">B C
+</span></body></html>
diff --git a/layout/base/crashtests/490376-1.xhtml b/layout/base/crashtests/490376-1.xhtml
new file mode 100644
index 0000000000..4ee606f923
--- /dev/null
+++ b/layout/base/crashtests/490376-1.xhtml
@@ -0,0 +1,15 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.body.innerHTML = "<table><caption><\/caption><iframe><\/iframe><\/table>";
+}
+
+]]>
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/490559-1.html b/layout/base/crashtests/490559-1.html
new file mode 100644
index 0000000000..972c1edaaa
--- /dev/null
+++ b/layout/base/crashtests/490559-1.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script type="text/javascript">
+function ddoe() {
+ var x=document.getElementById('a');
+ x.remove();
+}
+ </script>
+ </head>
+ <body onload="ddoe()">
+ <div style="width: 1px;">
+ <span id="a">&#68997;</span>
+&#68997;
+ </div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/490747.html b/layout/base/crashtests/490747.html
new file mode 100644
index 0000000000..837ad90234
--- /dev/null
+++ b/layout/base/crashtests/490747.html
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+div:first-letter { }
+</style>
+</head>
+<body onload="document.getElementById('s').style.height = '700px';" style="text-align: justify;"><div><span id="s" style="unicode-bidi: bidi-override;">Hello &#x064A;&#x0646;</span></div></body>
+</html>
diff --git a/layout/base/crashtests/49122-1.html b/layout/base/crashtests/49122-1.html
new file mode 100644
index 0000000000..7f2cc012f1
--- /dev/null
+++ b/layout/base/crashtests/49122-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"
+ "http://www.w3.org/TR/REC-html40/loose.dtd">
+<HTML>
+<HEAD>
+<TITLE>Mozilla Bug 49122</TITLE>
+</HEAD>
+<BODY><FORM action="">
+<TABLE>
+<TR>
+<TD>
+<MAP NAME="blah">
+<AREA SHAPE="rect" COORDS="0,1,1,0" href="" alt="blah">
+</MAP>
+<IMG src="" USEMAP="#blah" alt="blah">
+</TD>
+</TR>
+</TABLE>
+<P>
+</FORM></BODY>
+</HTML>
diff --git a/layout/base/crashtests/491547-1.xhtml b/layout/base/crashtests/491547-1.xhtml
new file mode 100644
index 0000000000..c2c0a28bcd
--- /dev/null
+++ b/layout/base/crashtests/491547-1.xhtml
@@ -0,0 +1,20 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<box id="a" style="display: list-item;">
+&amp;m&amp;m
+<box id="b"/>
+</box>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a::first-letter {float: right; }
+</style>
+
+<script xmlns="http://www.w3.org/1999/xhtml">
+function doe() {
+document.getElementById('a').style.direction = 'rtl';
+document.getElementById('b').style.direction = 'ltr';
+}
+setTimeout(doe, 100);
+</script>
+
+</window>
diff --git a/layout/base/crashtests/491547-2.xhtml b/layout/base/crashtests/491547-2.xhtml
new file mode 100644
index 0000000000..6191a0734a
--- /dev/null
+++ b/layout/base/crashtests/491547-2.xhtml
@@ -0,0 +1,31 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<box id="a" style="display: list-item;">
+&amp;c&amp;m&amp;q
+<box id="b"/>
+</box>
+
+<style xmlns="http://www.w3.org/1999/xhtml">
+#a::first-letter {float: right; color: red;}
+</style>
+
+<script xmlns="http://www.w3.org/1999/xhtml"><![CDATA[
+function run() {
+ for (var i = 0; i < 20; i++) {
+ document.documentElement.offsetWidth;
+ doe();
+ document.documentElement.offsetWidth;
+ undoe();
+ }
+}
+function doe() {
+ document.getElementById('a').style.direction = 'rtl';
+ document.getElementById('b').style.direction = 'ltr';
+}
+function undoe() {
+ document.getElementById('a').style.direction = 'ltr';
+ document.getElementById('b').style.direction = 'rtl';
+}
+setTimeout(run, 100);
+]]></script>
+</window>
diff --git a/layout/base/crashtests/492014.xhtml b/layout/base/crashtests/492014.xhtml
new file mode 100644
index 0000000000..b16622d691
--- /dev/null
+++ b/layout/base/crashtests/492014.xhtml
@@ -0,0 +1,4 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><body onload="document.getElementsByTagName('td')[0].appendChild(document.createElement('iframe'));">
+<caption></caption><td></td>
+</body>
+</html>
diff --git a/layout/base/crashtests/492112-1.xhtml b/layout/base/crashtests/492112-1.xhtml
new file mode 100644
index 0000000000..b3682a07bf
--- /dev/null
+++ b/layout/base/crashtests/492112-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css" id="s">
+
+div {
+ color: green;
+}
+
+</style>
+</head>
+
+<body onload="document.getElementById('s').disabled = true;"><colgroup> </colgroup></body>
+
+</html>
diff --git a/layout/base/crashtests/492163-1.xhtml b/layout/base/crashtests/492163-1.xhtml
new file mode 100644
index 0000000000..51cb1d3cb2
--- /dev/null
+++ b/layout/base/crashtests/492163-1.xhtml
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+
+function addSheet(text)
+{
+ var head = document.getElementsByTagName("head")[0];
+ var sheet = document.createElement("style");
+ sheet.appendChild(document.createTextNode(text));
+ head.appendChild(sheet);
+}
+
+</script>
+
+<style>colgroup:before { content: '0'; }</style>
+
+</head>
+
+<body onload="addSheet('x { }');"><table><colgroup></colgroup></table></body>
+</html>
diff --git a/layout/base/crashtests/495350-1.html b/layout/base/crashtests/495350-1.html
new file mode 100644
index 0000000000..ccab5b3731
--- /dev/null
+++ b/layout/base/crashtests/495350-1.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+<div style="display: -moz-inline-box;">
+<br style="position: fixed;">
+</div>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/496011-1.xhtml b/layout/base/crashtests/496011-1.xhtml
new file mode 100644
index 0000000000..1fabb4dbeb
--- /dev/null
+++ b/layout/base/crashtests/496011-1.xhtml
@@ -0,0 +1,20 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ document.body.setAttribute("contenteditable", "true");
+ document.execCommand("selectAll", false, null);
+ document.execCommand("inserthtml", false, "<span><div><\/div><\/span>");
+ document.execCommand("undo", false, null);
+}
+
+]]>
+</script>
+</head>
+
+<body onload="boom();"><textarea><span/></textarea></body>
+
+</html>
diff --git a/layout/base/crashtests/499741-1.xhtml b/layout/base/crashtests/499741-1.xhtml
new file mode 100644
index 0000000000..d2d1c65960
--- /dev/null
+++ b/layout/base/crashtests/499741-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="column-width: 1px;">a<div><span><wbr/>a<select/></span></div></html>
diff --git a/layout/base/crashtests/499841-1.xhtml b/layout/base/crashtests/499841-1.xhtml
new file mode 100644
index 0000000000..28d0ec9122
--- /dev/null
+++ b/layout/base/crashtests/499841-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body onload="document.getElementById('v').appendChild(document.getElementById('s'));">
+<style id="s">div:first-letter { float: right; } </style><div id="v"><span>AB</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/499858-1.xhtml b/layout/base/crashtests/499858-1.xhtml
new file mode 100644
index 0000000000..5f9c573b28
--- /dev/null
+++ b/layout/base/crashtests/499858-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="word-wrap: break-word; padding: 0pt 3870px; position: relative; column-count: 3;">
+<body onload="document.documentElement.style.visibility='hidden';">
+<div>,,, <span style="position: absolute;"><div/>2</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/500467-1.html b/layout/base/crashtests/500467-1.html
new file mode 100644
index 0000000000..a2bf5a7c4f
--- /dev/null
+++ b/layout/base/crashtests/500467-1.html
@@ -0,0 +1,23141 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script>
+ function doIt() {
+ var p = document.getElementById("p").tBodies[0];
+ var nodes = Array.prototype.slice.call(document.getElementsByTagName("tr"));
+ for (var i = 0; i < nodes.length; ++i) {
+ var n = nodes[i].nextSibling;
+ p.removeChild(nodes[i]);
+ p.insertBefore(nodes[i], n);
+ }
+ setTimeout('document.documentElement.className = ""', 0);
+ }
+ </script>
+ </head>
+ <body onload="doIt()">
+ <table id="p">
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ <tr>
+ <td>Text</td>
+ <td>Text</td>
+ <td>Text</td>
+ </tr>
+ </table>
+ </body>
+</html>
diff --git a/layout/base/crashtests/501878-1.html b/layout/base/crashtests/501878-1.html
new file mode 100644
index 0000000000..adecb96a96
--- /dev/null
+++ b/layout/base/crashtests/501878-1.html
@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+<script type='text/javascript'>
+window.addEventListener("load", function(){ document.getElementById("x").appendChild(document.createTextNode(" ")); });
+</script>
+<text id="x"><text style="position: absolute;"/>Hello</text></svg>
diff --git a/layout/base/crashtests/50257-1.html b/layout/base/crashtests/50257-1.html
new file mode 100644
index 0000000000..a7dfd7b9a9
--- /dev/null
+++ b/layout/base/crashtests/50257-1.html
@@ -0,0 +1,20 @@
+<html>
+<body>
+<div style="margin-bottom: -1">
+<img height=1>
+</div>
+<table align=left>
+ <td>
+ <table>
+ <td>
+ </table>
+ </td>
+ <td>
+ <table cols=2>
+ <td>
+ </table>
+ </td>
+</table>
+<br clear="left">
+</body>
+</html>
diff --git a/layout/base/crashtests/503936-1.html b/layout/base/crashtests/503936-1.html
new file mode 100644
index 0000000000..c1612e8a9d
--- /dev/null
+++ b/layout/base/crashtests/503936-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var st = document.getElementById("s").firstChild
+
+ var range = document.createRange();
+ range.setStart(document.documentElement, 0);
+ range.setEnd(st, 1);
+ range.deleteContents()
+
+ try { range.surroundContents(st); } catch(e) { }
+}
+
+</script>
+
+<style type="text/css">
+
+div:first-letter { float: left; }
+
+</style>
+</head>
+
+<body onload="boom();"><div><span id="s">Foo</span></div></body>
+
+</html>
diff --git a/layout/base/crashtests/50395-1.html b/layout/base/crashtests/50395-1.html
new file mode 100644
index 0000000000..42fc8d786c
--- /dev/null
+++ b/layout/base/crashtests/50395-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
+<html>
+<head>
+ <title>Testcase for bug 50395</title>
+ <style> * { overflow: auto; } </style>
+</head>
+<body>
+<h3>In head: &lt;style&gt; * { overflow: auto; } &lt;/style&gt;</h3>
+
+<p>iframe width="40%":</p>
+<iframe width="40%"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+<p>iframe height="10%"</p>
+<iframe height="10%"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+<p>iframe height="90"</p>
+<iframe height="90"
+ src="../../../testing/crashtest/images/600x58.png"></iframe>
+
+
+</body>
+</html>
diff --git a/layout/base/crashtests/507119.html b/layout/base/crashtests/507119.html
new file mode 100644
index 0000000000..83cb3b20ad
--- /dev/null
+++ b/layout/base/crashtests/507119.html
@@ -0,0 +1,554 @@
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
+<div style="font-family:verdana, helvetica, sans-serif;font-size:8pt">
diff --git a/layout/base/crashtests/522374-1.html b/layout/base/crashtests/522374-1.html
new file mode 100644
index 0000000000..1dfbc2b815
--- /dev/null
+++ b/layout/base/crashtests/522374-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html><html class="reftest-wait"><head><script type="text/javascript">
+
+function boom()
+{
+ var area = document.getElementById("area");
+ var main = document.getElementById("main");
+
+ area.nextSibling.data += " a ";
+ document.documentElement.offsetHeight;
+ area.nextSibling.data = " b ";
+ main.previousSibling.data += " \u062A ";
+
+ document.documentElement.removeAttribute("class");
+}
+
+function boom0(ev)
+{
+ setTimeout(boom, 0);
+}
+
+</script></head><body onload="boom0();"> <div id="main" style="width: 1px;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC" usemap="#Map"><map name="Map"><area id="area"> </map></div></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/522374-2.html b/layout/base/crashtests/522374-2.html
new file mode 100644
index 0000000000..934a6649d4
--- /dev/null
+++ b/layout/base/crashtests/522374-2.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html><html class="reftest-wait"><head><script type="text/javascript">
+
+function boom()
+{
+ var area = document.getElementById("area");
+ var main = document.getElementById("main");
+
+ area.nextSibling.data += " a ";
+ document.documentElement.offsetHeight;
+ area.nextSibling.data = " b ";
+ main.previousSibling.data += " \u042A ";
+
+ document.documentElement.removeAttribute("class");
+}
+
+function boom0(ev)
+{
+ setTimeout(boom, 0);
+}
+
+</script></head><body onload="boom0();"> <div id="main" style="width: 1px;"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC" usemap="#Map"><map name="Map"><area id="area"> </map></div></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/526378-1.xhtml b/layout/base/crashtests/526378-1.xhtml
new file mode 100644
index 0000000000..1fbd42092f
--- /dev/null
+++ b/layout/base/crashtests/526378-1.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var x = document.getElementById("x");
+
+ x.appendChild(document.createTextNode("A"));
+ x.appendChild(document.createTextNode("\u202B" + "C"));
+
+ document.documentElement.clientHeight; // flush layout
+
+ x.normalize();
+ x.appendChild(document.createTextNode("D"));
+}
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<box id="x" style="display:inline"><box/></box>
+</window>
diff --git a/layout/base/crashtests/534367-1.xhtml b/layout/base/crashtests/534367-1.xhtml
new file mode 100644
index 0000000000..3e8de11c1b
--- /dev/null
+++ b/layout/base/crashtests/534367-1.xhtml
@@ -0,0 +1,29 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style type="text/css">
+
+td:first-letter { }
+
+</style>
+
+<script type="text/javascript">
+
+function boom()
+{
+ var tbody = document.getElementById("tbody");
+ document.documentElement.offsetHeight;
+ tbody.style.direction = "rtl";
+ document.documentElement.offsetHeight;
+ tbody.style.direction = "";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+
+</head>
+<body onload="boom();">
+
+<table><tbody id="tbody"><tr><td><span>1 2</span></td></tr></tbody></table>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/534368-1.xhtml b/layout/base/crashtests/534368-1.xhtml
new file mode 100644
index 0000000000..6b62e06049
--- /dev/null
+++ b/layout/base/crashtests/534368-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="position: absolute; font-size: 1000px; column-count: 3;">
+<script>
+
+function boom()
+{
+ var newSS = document.createElementNS("http://www.w3.org/1999/xhtml", "style");
+ newSS.appendChild(document.createTextNode("whattheheck:first-line {}"));
+ document.getElementById("h").appendChild(newSS);
+}
+window.addEventListener("load", boom, false);
+
+</script>
+<head id="h" style="overflow-x: scroll; display: block;"><style style="position: absolute; display: block;">zz ]</style><style style="display: none;">[</style><style style="display: none;">[e='zz']:nth-last-child(odd) {}</style><style style="display: none;"></style><style style="display: none;">[c]</style><style style="display: none;">[c]</style><style style="display: none;">[class='zzzzz'] {}</style></head>
+</html>
diff --git a/layout/base/crashtests/534768-1.html b/layout/base/crashtests/534768-1.html
new file mode 100644
index 0000000000..17c9ac68fe
--- /dev/null
+++ b/layout/base/crashtests/534768-1.html
@@ -0,0 +1,23 @@
+<html style="direction: rtl;">
+<head>
+<style>
+
+body:after { content: '0'; }
+body:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.style.direction = "";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.textIndent = "3px";
+}
+
+</script>
+</head>
+
+<body onload="boom();"> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/534768-2.html b/layout/base/crashtests/534768-2.html
new file mode 100644
index 0000000000..67ecb4b6bd
--- /dev/null
+++ b/layout/base/crashtests/534768-2.html
@@ -0,0 +1,22 @@
+<html style="direction: rtl;">
+<head>
+<style>
+
+body:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ document.documentElement.style.direction = "";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.textIndent = "3px";
+}
+
+</script>
+</head>
+
+<body onload="boom();"> &#x202E;</body>
+
+</html>
diff --git a/layout/base/crashtests/535721-1.xhtml b/layout/base/crashtests/535721-1.xhtml
new file mode 100644
index 0000000000..cd5696d30c
--- /dev/null
+++ b/layout/base/crashtests/535721-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+function boom()
+{
+
+ document.getElementById("i").appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "div"));
+
+}
+</script>
+</head>
+<body onload="boom();">
+
+<div><span><span id="i"><div></div></span></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/535911-1.xhtml b/layout/base/crashtests/535911-1.xhtml
new file mode 100644
index 0000000000..181086c54b
--- /dev/null
+++ b/layout/base/crashtests/535911-1.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ var s = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var b = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box");
+ s.appendChild(b);
+ document.getElementById("a").appendChild(s);
+}
+
+</script>
+</head>
+<body onload="boom();" style="column-width: 1px;"><span id="a"><box xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"/></span><div>Hello</div></body>
+</html>
diff --git a/layout/base/crashtests/536720.xhtml b/layout/base/crashtests/536720.xhtml
new file mode 100644
index 0000000000..811cd8a9f8
--- /dev/null
+++ b/layout/base/crashtests/536720.xhtml
@@ -0,0 +1,23 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var menupopup = document.getElementById("menupopup");
+ menupopup.remove();
+}
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><menulist><menupopup id="menupopup"/>
+T
+</menulist></window>
+
+</window>
diff --git a/layout/base/crashtests/537562-1.xhtml b/layout/base/crashtests/537562-1.xhtml
new file mode 100644
index 0000000000..0e304bb632
--- /dev/null
+++ b/layout/base/crashtests/537562-1.xhtml
@@ -0,0 +1,10 @@
+<html xmlns="http://www.w3.org/1999/xhtml" id="a">
+<head>
+<style>
+#a { column-count: 2; }
+#a:first-letter { }
+</style>
+</head>
+<body id="b" onload="document.getElementById('b').appendChild(document.createElement('tr'));"></body>
+X
+</html>
diff --git a/layout/base/crashtests/537624-1.html b/layout/base/crashtests/537624-1.html
new file mode 100644
index 0000000000..a6c19516ee
--- /dev/null
+++ b/layout/base/crashtests/537624-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ var a = document.getElementById("a");
+ var b = document.getElementById("b");
+ a.insertBefore(b, a.firstChild);
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="a"><div></div></span><span id="b"><span style="display: none;"></span><span style="display: none;"></span></span></body>
+
+</html>
diff --git a/layout/base/crashtests/537631-1.html b/layout/base/crashtests/537631-1.html
new file mode 100644
index 0000000000..7a12e646c1
--- /dev/null
+++ b/layout/base/crashtests/537631-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<head></head>
+<body style="position: fixed; column-count: 2;"><div style="position: absolute; height: 7em;"><br><br></div></body>
+</html>
diff --git a/layout/base/crashtests/538082-1.xhtml b/layout/base/crashtests/538082-1.xhtml
new file mode 100644
index 0000000000..10335617ea
--- /dev/null
+++ b/layout/base/crashtests/538082-1.xhtml
@@ -0,0 +1,34 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:m="http://www.w3.org/1998/Math/MathML">
+
+<script type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var e = document.getElementById("e");
+ var g = document.getElementById("maligngroup");
+
+ var M = "http://www.w3.org/1998/Math/MathML";
+ var a = document.createElementNS(M, "mfrac");
+ var b = document.createElementNS(M, "ms");
+ var c = document.createElementNS(M, "merror");
+
+ g.appendChild(c);
+
+ a.appendChild(b);
+ e.appendChild(a);
+}
+
+
+window.addEventListener("load", boom, false);
+
+]]>
+</script>
+
+<m:math><box id="e"><m:mo><m:ms/><box style="display: inline;"><box><m:maligngroup id="maligngroup"/></box></box></m:mo></box></m:math>
+
+</window>
diff --git a/layout/base/crashtests/538207-1.xhtml b/layout/base/crashtests/538207-1.xhtml
new file mode 100644
index 0000000000..f893e28374
--- /dev/null
+++ b/layout/base/crashtests/538207-1.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:m="http://www.w3.org/1998/Math/MathML">
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("iframe").appendChild(document.createElement("span"));
+ document.getElementById("mrow").appendChild(document.createElement("span"));
+}
+
+</script>
+</head>
+<body onload="boom();"><m:mrow id="mrow"><iframe id="iframe"/></m:mrow></body>
+</html>
diff --git a/layout/base/crashtests/538210-1.html b/layout/base/crashtests/538210-1.html
new file mode 100644
index 0000000000..7070f8e997
--- /dev/null
+++ b/layout/base/crashtests/538210-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<script>
+function boom()
+{
+ var frameset = document.getElementsByTagName("frameset")[0]
+ var oldFrame = frameset.firstChild;
+ var newFrame = document.createElementNS("http://www.w3.org/1999/xhtml", "frame");
+ frameset.appendChild(newFrame);
+ frameset.removeChild(oldFrame);
+}
+</script>
+</head>
+<frameset onload="boom()"><frame></frame></frameset>
+</html>
+
diff --git a/layout/base/crashtests/538267-1.html b/layout/base/crashtests/538267-1.html
new file mode 100644
index 0000000000..2bf73d1971
--- /dev/null
+++ b/layout/base/crashtests/538267-1.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+<style>
+div:first-letter { float: left; }
+div { column-count: 2; width: 0; }
+</style>
+<script>
+function boom()
+{
+ var v = document.getElementById("v");
+ v.firstChild.remove();
+}
+</script>
+</head>
+<body onload="boom();">
+<div id="v">a b<span>c</span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/540760.xhtml b/layout/base/crashtests/540760.xhtml
new file mode 100644
index 0000000000..b0e857ec9f
--- /dev/null
+++ b/layout/base/crashtests/540760.xhtml
@@ -0,0 +1,18 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<script>
+
+function boom()
+{
+ var a = document.getElementById("a");
+ while (a.firstChild)
+ a.removeChild(a.firstChild);
+}
+
+window.addEventListener("load", boom, false);
+
+</script>
+
+<menulist id="a" sizetopopup="pref"><menupopup/><menupopup/></menulist>
+
+</window>
diff --git a/layout/base/crashtests/540771-1.xhtml b/layout/base/crashtests/540771-1.xhtml
new file mode 100644
index 0000000000..3830e148d6
--- /dev/null
+++ b/layout/base/crashtests/540771-1.xhtml
@@ -0,0 +1,18 @@
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+<xul:deck id="d"/>
+
+<xul:menuitem><span /><xul:menupopup id="p"/></xul:menuitem>
+
+<script>
+function boom()
+{
+ var p = document.getElementById("p");
+ var d = document.getElementById("d");
+ p.parentNode.removeChild(p);
+ d.appendChild(document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "box"));
+}
+window.addEventListener("load", boom, false);
+</script>
+
+</html>
diff --git a/layout/base/crashtests/541869-1.xhtml b/layout/base/crashtests/541869-1.xhtml
new file mode 100644
index 0000000000..b25c2b1f34
--- /dev/null
+++ b/layout/base/crashtests/541869-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="right: 10000px; top: 11121164%; position: fixed;"><div>X</div>
+<script>
+window.addEventListener("load", function() { document.documentElement.style.letterSpacing = '21em'; }, false);
+</script>
+</html>
diff --git a/layout/base/crashtests/541869-2.html b/layout/base/crashtests/541869-2.html
new file mode 100644
index 0000000000..2800150db3
--- /dev/null
+++ b/layout/base/crashtests/541869-2.html
@@ -0,0 +1,5 @@
+<html style="padding: 9007199254740991%;">
+<body onload="document.getElementById('f').style.border = 'none';" style="display: inline">
+<iframe id="f"></iframe>
+</body>
+</html>
diff --git a/layout/base/crashtests/543648-1.html b/layout/base/crashtests/543648-1.html
new file mode 100644
index 0000000000..dff9440d10
--- /dev/null
+++ b/layout/base/crashtests/543648-1.html
@@ -0,0 +1 @@
+<html style="-moz-border-top-colors: red; border: 10000000px solid yellow;"><body></body></html>
diff --git a/layout/base/crashtests/560447-1.html b/layout/base/crashtests/560447-1.html
new file mode 100644
index 0000000000..e6d4f9cb4e
--- /dev/null
+++ b/layout/base/crashtests/560447-1.html
@@ -0,0 +1 @@
+<html><body onload="setTimeout(function(){document.getElementById('m').appendChild(document.createElement('area'));},0);"><map id="m" name="m"></map><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#m"></body></html>
diff --git a/layout/base/crashtests/564063-1.html b/layout/base/crashtests/564063-1.html
new file mode 100644
index 0000000000..eb288982e8
--- /dev/null
+++ b/layout/base/crashtests/564063-1.html
@@ -0,0 +1,20 @@
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.getElementById("m"));
+ document.getElementsByTagName("area")[0].appendChild(document.createTextNode('x'));
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 200);">
+
+<map name="m" id="m"><area></map><img src="data:image/gif,GIF89a1%00%3C%00%D5%FF%00%9D%B6%85%18%1C%14%8E%A4xz%8Dg%3AC1%9F%B6%86%A3%B8%89%1F%23%1A%9C%AD%85(%2C!%AD%BC%93%0A%0B%08bkP%BC%C2%A0PP%3E%C7%C5%A9%BD%B4%85%13%11%0C%CA%B8%8A%CE%B6%85%B7%A2v3.%24%D0%B7%88%9F%8Ch%82rU%D2%BA%8D%D8%BF%9B%A2%94%80%D7%C9%B5%26!%1AC9.%C7%AD%96%EB%C6%B5%E6%CB%BE%AD%8F%88%F1%CB%C2%C0%C0%C0%F6%CC%C7%BF%9B%99%D2%A9%A8%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00!%F9%04%01%00%00%24%00%2C%00%00%00%001%00%3C%00%00%06%FF%40%92pH%2C%1A%8F%C4%D2%A8%84l%3AI%A5(%D3Y%0A%3D%14%06%83%E2%11%9A%3E%8DU%0E%E7%F1h4%1E%9CPh%09%25%86%1A%06%80%1CP0%9CC%DFa%89%03%2F%CC%FFu%0Af%0D%1CL%23%0F%06~%7Fru%1Cy%24%1C%0As%03%94%95%03%07%98%07%04%0C%08gp%02%0C%15%09%A3%A3%15%0C%00%06xO%23%0D~%03%09%04%B1%B1%07%0B%A7r%03%B1u%05%A0%0C%02%8B%02%0E%0E%00%0D_%1Cq%B8%03%8B%09%C9%8B%0E%B6%CE%8B%93%09%00%0A%5EG%25%0D%A0%04%CC%7F%CB%D1%03%0E%A2%0B%A4%DB%8B(%A8%23N%23%0A%0C%E5%D1%DE%8B%ED%B6%93%04%E6%E8N!%02%F0%D1%FC%97%DC%DD%F6%14%A4k%12%02%1A%BF%83%B8%0EN%FBsNA%17%82%0B%0F%F2%13%F0%EF%8F0%00%95%CE%D9)%84%24DD%89%20%0F%3A%20%B0%60%5B%1D%03%8E%8Ex%0C%C9%12%24%B2%2C)%8B%1C%FA%D8%B2%E6%1Cg0eF2%F0%D1AE%FF%9B%08%094J%82HN%C4K%BE%80%86%C45t%88%B19%A6%7C%05S%CA%92)%CA!%EB%E48%40%C1U%DCO%AA%8B%12*%88%19%E2X%BDn%F3%C0%F2cp%D1%A1%90%B2%006%85%25%90%14%A3Z%06%A7%F0%26-%F0%40%08%079%07%EA%CEa%9B%F4%80Z%01%01%128%10%80%82Y5H%80E%1A%06%10%400P%C6%B7%CA%09%84%0C%60r%3F9l%D5%02%80%970%15%24%3F%9EA%5E%04%BB%CF%F0U%B8%A9EK%C4%3C%C70_%12Y%DD%C9%3E%08%2C%ED%E4%06Q%1A%00%60%40s%F7%9F%D0%7Flo~%3A%A0%B2qs4%05(%C7S%02Q%01%06%07%02%60%FA%FA%BC%1D%1D%B7%B8%AF%F8%D1%FD%FC%97%BB%CDB%F6H%22_%1E%B4w%3A%C4%88%16%60%DF%3E%163%D3E%CA%D2%07%E0%93Q%81%FF%00%D6%A4%5B%7C2)%B0_9%06%40%A0%E0%82Y(%F2%07Es%08%40%17*%0D%0Cd%C4%03%84E%B3%8D%01%1B%400%FF%C1%87%1FZ%20%01%0A%13%CA%01L%89xi%F1%80%85F%84%60%E0j%B5%09%A0%803%20%D6%88%C2T%A0%05%93%89%03%08p%C0%06%3E%08%8C%C4%CD%2B%09h%40%81%075%82%88%82%04%09%90h_%16*%B2%F8%84%8B%EDd'K%05%1ALPA%92%1F%A20%81%06%0Dl%B0%81%09j%A8!e%1Ea%5C!%E6%06%1D0p%C1%96%5Czy%E6%23Txq%82%08or9%81%97t%F6y%84%96z%F2%E9%E7%A0B%00%1A%A7%05%84%12jh%92(d%90%E8%A0%8B%DA%98%C1%9C%8F6%11%A9%92%93V%FA%C8%08G%06j%01%A5%9A%16%A1%01%5E%81N%00B%A8_P%E0%00%05%A5%9E%8Aj%13%20%60%E0%00%88%16%D4X%C1%05%1A%BCz%C4%08%20%A8z%C1%87%17%D4z%EB%04oZ%E0%EA%A0'%0Ct%82%09%CC6%CB%EC%05%0E%60%F0!%04%1D%7C%D8A%02%AC%B2u%81%AB%234%7B%C2%17%A1TPA0%E4%92%EBA%04%B3N%20%91k%BA%1D%60%80%E4%04%1E%5C%2B%EE%B8%C1T%E0%C1%BD%F8z%80A%1E%BC%C2%1B%01%B1%15D%F0.%BC%D0%FEJA%05%18%80%60%CD%A3%C4u%D0%81%BE%F6b%F0%26%AB%EA%3A%00-%AB%05%7F8%02%A8%AB%88%90%80%87%D0zpA%06%19X%80A%02%BFB%20%F2%C9%F7%BA%E9%E1%87%99%FA%99%A5%06%25%5B%AC%01%1B%BC%BEy%AB%AC%C0%5E%20%2B%0A%F3N%60A%AE%BA%0A1%82%05%17%24%20%B1%D0!f%A0%C1%9A%1Ahpl%D1%24%80%F0A%07NS%ED%A7%09%95%06%01%00%3B" usemap="#m">
+
+</body>
+</html>
diff --git a/layout/base/crashtests/56746-1.html b/layout/base/crashtests/56746-1.html
new file mode 100644
index 0000000000..83215467db
--- /dev/null
+++ b/layout/base/crashtests/56746-1.html
@@ -0,0 +1,16 @@
+<html>
+<head>
+<title>Example 8</title>
+</head>
+<body>
+<FORM METHOD="GET" ACTION="" NAME="searchform">
+ <BUTTON type=submit >
+ <table>
+ <tr>
+ <td>CELL 1</td>
+ </tr>
+ </table>
+ </BUTTON>
+</FORM>
+</body>
+</html>
diff --git a/layout/base/crashtests/569018-1.html b/layout/base/crashtests/569018-1.html
new file mode 100644
index 0000000000..557787f66d
--- /dev/null
+++ b/layout/base/crashtests/569018-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script type="text/javascript">
+
+function boom()
+{
+ document.getElementById("x").appendChild(document.getElementsByTagName("map")[0]);
+ document.getElementsByTagName("area")[0].appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", 'span'));
+ document.body.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", 'td'));
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 30);"><img src="data:image/gif,GIF87a%02%00%02%00%B3%00%00%00%00%00%FF%FF%FF%00%00%00%00%00%00%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%2C%00%00%00%00%02%00%02%00%00%04%03%90H%12%00%3B" usemap="#Map"><map name="Map"><area></map><span id="x"></span></body>
+</html>
diff --git a/layout/base/crashtests/572003.xhtml b/layout/base/crashtests/572003.xhtml
new file mode 100644
index 0000000000..952660cf45
--- /dev/null
+++ b/layout/base/crashtests/572003.xhtml
@@ -0,0 +1,3 @@
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="var p=document.getElementById('p'); for(var i=0;i!=3;++i)p.parentNode.appendChild(p);">
+<menuitem style="counter-reset: chicken;">P<menupopup id="p" style="counter-reset: chicken;"/></menuitem>
+</window>
diff --git a/layout/base/crashtests/572582-1.xhtml b/layout/base/crashtests/572582-1.xhtml
new file mode 100644
index 0000000000..00916b949c
--- /dev/null
+++ b/layout/base/crashtests/572582-1.xhtml
@@ -0,0 +1,25 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="width: 1px">
+<head>
+<script style="display: none;" id="fuzz1" type="text/javascript">
+<![CDATA[
+
+function boom()
+{
+ var span = document.createElementNS("http://www.w3.org/1999/xhtml", "span")
+ var t1 = document.createTextNode("\uD1B5");
+ span.appendChild(t1);
+ var t2 = document.createTextNode("");
+ span.appendChild(t2);
+ var t3 = document.createTextNode("\u200Bq");
+ span.appendChild(t3);
+ document.documentElement.appendChild(span);
+ document.documentElement.offsetHeight;
+ t3.data = "\u062A";
+}
+
+window.addEventListener("load", boom);
+
+]]>
+</script>
+</head>
+</html>
diff --git a/layout/base/crashtests/576649-1.html b/layout/base/crashtests/576649-1.html
new file mode 100644
index 0000000000..e8341fef46
--- /dev/null
+++ b/layout/base/crashtests/576649-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('p').style.fontWeight = 'bold';" style="letter-spacing: 2251799813685247pt; column-count: 11; position: absolute;"><p id="p"><span style="position: absolute;">C d s</span></p></body>
+</html>
diff --git a/layout/base/crashtests/579655.html b/layout/base/crashtests/579655.html
new file mode 100644
index 0000000000..460fa34c2f
--- /dev/null
+++ b/layout/base/crashtests/579655.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+<script type="text/javascript">
+function load()
+{
+ var bs=document.getElementById("b");
+ if(bs.lastChild){
+ bs.lastChild.textContent="text\/css";
+ bs.textContent="head";
+ }
+}
+</script>
+<body onload="load();">
+ <table >
+ <td>
+ <p id="b">aaaa aaaa aaa aa aaaa aa aaaaaa aaaa aa aa aaa aaa aaaa aaaaaaa aaa aaaa aa a aaaa aaaa aaaaaaa aa aa aa aa aaaaaaa .</p>
+ <body style="white-space:pre-wrap" >
+ </body>
+ </td>
+ <td dir="rtl" >
+ <body contenteditable="true">
+ </body>
+ </td>
+ </table>
+</body>
+</html>
diff --git a/layout/base/crashtests/580129-1.html b/layout/base/crashtests/580129-1.html
new file mode 100644
index 0000000000..625dd8086b
--- /dev/null
+++ b/layout/base/crashtests/580129-1.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var a = document.documentElement;
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ b.setAttributeNS(null, "style", "column-width: 20em;");
+ a.innerHTML = "<frameset>";
+ b.innerHTML = "<dd><marquee>x";
+ document.removeChild(a);
+ document.appendChild(b);
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/580494-1.html b/layout/base/crashtests/580494-1.html
new file mode 100644
index 0000000000..c76125f74b
--- /dev/null
+++ b/layout/base/crashtests/580494-1.html
@@ -0,0 +1 @@
+<html><body><marquee><video></video></marquee></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/580834-1.xhtml b/layout/base/crashtests/580834-1.xhtml
new file mode 100644
index 0000000000..0a61c52016
--- /dev/null
+++ b/layout/base/crashtests/580834-1.xhtml
@@ -0,0 +1,5 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<menuitem xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><tooltip/><vbox xmlns="http://www.w3.org/1999/xhtml"></vbox></menuitem>
+</body>
+</html>
diff --git a/layout/base/crashtests/589787.html b/layout/base/crashtests/589787.html
new file mode 100644
index 0000000000..76c21a2fab
--- /dev/null
+++ b/layout/base/crashtests/589787.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function boom() {
+ document.documentElement.offsetHeight;
+ document.getElementById('e').setAttribute('style', '');
+ document.documentElement.offsetHeight;
+}
+</script>
+<style id="e">
+body #a::after { content: "before text"; position: fixed; }
+</style>
+</head>
+
+<body onload="boom();" style="column-count: 2; width: 100px;">
+<div>m</div>
+<div id="a" style="column-count: 2;">
+m
+<br style="float: left;">
+m
+<span style="float: left;">m</span>
+
+<div style="float: left; column-width: 9999999999px;"></div>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/591075-1.html b/layout/base/crashtests/591075-1.html
new file mode 100644
index 0000000000..958aa26a4b
--- /dev/null
+++ b/layout/base/crashtests/591075-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="max-width: max-content"><body style="max-width: 210708270904025mozmm"></body></html>
diff --git a/layout/base/crashtests/591998-1.html b/layout/base/crashtests/591998-1.html
new file mode 100644
index 0000000000..ac461dab86
--- /dev/null
+++ b/layout/base/crashtests/591998-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="border: 168691114px solid green"><body ></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/595039-1.html b/layout/base/crashtests/595039-1.html
new file mode 100644
index 0000000000..3b9a2fab0c
--- /dev/null
+++ b/layout/base/crashtests/595039-1.html
@@ -0,0 +1 @@
+<html><body><div style="height: 100px; background-image: linear-gradient(to bottom right, yellow, blue); background-size: 4398046511104mozmm;"></div></body></html>
diff --git a/layout/base/crashtests/597924-1.html b/layout/base/crashtests/597924-1.html
new file mode 100644
index 0000000000..d855997eec
--- /dev/null
+++ b/layout/base/crashtests/597924-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementById("s").appendChild(document.createElement("div"));
+ var marq = document.getElementById("f").contentDocument.documentElement;
+ marq.behavior = "alternate";
+}
+
+</script>
+</head>
+<body onload="boom();"><span id="s"></span><iframe src="data:text/xml,%3Cmarquee%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxhtml%22%3EX%3C%2Fmarquee%3E" id="f"></iframe></body>
+</html>
diff --git a/layout/base/crashtests/606432-1.html b/layout/base/crashtests/606432-1.html
new file mode 100644
index 0000000000..4473778723
--- /dev/null
+++ b/layout/base/crashtests/606432-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+function boom()
+{
+ var t = document.getElementById("f").contentDocument.documentElement;
+ t.contentEditable = "true";
+ t.focus();
+ document.body.appendChild(t);
+ setTimeout(finish, 0);
+}
+
+function finish()
+{
+ document.documentElement.className = "";
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 200);"><iframe id="f" srcdoc="<html xmlns='http://www.w3.org/1999/xhtml'></html>"></iframe></body>
+</html>
diff --git a/layout/base/crashtests/609821-1.xhtml b/layout/base/crashtests/609821-1.xhtml
new file mode 100644
index 0000000000..bd2feb9c0f
--- /dev/null
+++ b/layout/base/crashtests/609821-1.xhtml
@@ -0,0 +1,17 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+<![CDATA[
+
+function boom()
+{
+ var td = document.getElementById("td");
+ td.contentEditable = "true";
+ td.focus();
+}
+
+]]>
+</script></head>
+
+<body onload="boom();"><td id="td"/></body>
+</html>
diff --git a/layout/base/crashtests/615146-1.html b/layout/base/crashtests/615146-1.html
new file mode 100644
index 0000000000..b7b3cb2790
--- /dev/null
+++ b/layout/base/crashtests/615146-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><svg requiredExtensions=e><foreignObject>
diff --git a/layout/base/crashtests/615781-1.xhtml b/layout/base/crashtests/615781-1.xhtml
new file mode 100644
index 0000000000..2be96daa4b
--- /dev/null
+++ b/layout/base/crashtests/615781-1.xhtml
@@ -0,0 +1,22 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+
+<input id="i"/>
+
+<script>
+<![CDATA[
+
+function boom()
+{
+ var i = document.getElementById("i");
+ i.select();
+ i.setAttribute("type", "radio");
+ i.blur();
+ document.documentElement.removeAttribute("class");
+}
+
+window.addEventListener("load", function() { setTimeout(boom, 100); });
+
+]]>
+</script>
+
+</html>
diff --git a/layout/base/crashtests/616495-single-side-composite-color-border.html b/layout/base/crashtests/616495-single-side-composite-color-border.html
new file mode 100644
index 0000000000..13bcad030a
--- /dev/null
+++ b/layout/base/crashtests/616495-single-side-composite-color-border.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title> Composite Color Crash Test </title>
+
+ <style>
+ .kaboom {
+ margin: 100px;
+ border-width: 20px 20px 20px 20px;
+ -moz-border-top-colors: green red green;
+ border-style: solid;
+ width: 70px;
+ height: 70px;
+ }
+ </style>
+ </head>
+
+ <body>
+ <div id="box" class="kaboom"></div>
+ </body>
+</html>
diff --git a/layout/base/crashtests/629035-1.html b/layout/base/crashtests/629035-1.html
new file mode 100644
index 0000000000..ef1cfafae1
--- /dev/null
+++ b/layout/base/crashtests/629035-1.html
@@ -0,0 +1,3 @@
+<script>
+ document.dir = "rtl";
+</script>
diff --git a/layout/base/crashtests/629908-1.html b/layout/base/crashtests/629908-1.html
new file mode 100644
index 0000000000..49b978597e
--- /dev/null
+++ b/layout/base/crashtests/629908-1.html
@@ -0,0 +1,9 @@
+<body onload="die()">
+ <script>
+ function die() {
+ document.body.offsetWidth;
+ document.removeChild(document.documentElement);
+ document.dir = "rtl";
+ }
+ </script>
+</body>
diff --git a/layout/base/crashtests/635329.html b/layout/base/crashtests/635329.html
new file mode 100644
index 0000000000..15153bda23
--- /dev/null
+++ b/layout/base/crashtests/635329.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="margin-left: 100%">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.lastChild.data = "\u062A"; // ARABIC LETTER TEH
+ document.body.lastChild.data += "Y";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span>A</span> B C</body>
+</html>
diff --git a/layout/base/crashtests/636229-1.html b/layout/base/crashtests/636229-1.html
new file mode 100644
index 0000000000..2ec6cafeea
--- /dev/null
+++ b/layout/base/crashtests/636229-1.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html>
+<html style="clip-path: url(&quot;#404&quot;); overflow: clip;"><body style="height: 400px; outline: 171787972850px solid green;"></body></html>
diff --git a/layout/base/crashtests/640272-empty.html b/layout/base/crashtests/640272-empty.html
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/layout/base/crashtests/640272-empty.html
diff --git a/layout/base/crashtests/640272-ref.html b/layout/base/crashtests/640272-ref.html
new file mode 100644
index 0000000000..951c0ae4bc
--- /dev/null
+++ b/layout/base/crashtests/640272-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 640272</title>
+<style>
+#waBackButton {
+ border: 1px solid blue;
+}
+</style>
+</head>
+<body>
+ <a href="index.html" id="waBackButton">Indietro</a>
+</body>
+</html>
diff --git a/layout/base/crashtests/640272.html b/layout/base/crashtests/640272.html
new file mode 100644
index 0000000000..0df1df96a0
--- /dev/null
+++ b/layout/base/crashtests/640272.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 640272</title>
+<style>
+#waBackButton {
+ border: 1px solid blue;
+ border-image: url(640272-empty.html) 0 10 0 15;
+}
+</style>
+</head>
+<body>
+ <a href="index.html" id="waBackButton">Indietro</a>
+</body>
+</html>
diff --git a/layout/base/crashtests/645193.html b/layout/base/crashtests/645193.html
new file mode 100644
index 0000000000..6341b3853b
--- /dev/null
+++ b/layout/base/crashtests/645193.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html style="direction: rtl; column-width: 1px;"><head><script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.body.style.unicodeBidi = "bidi-override";
+ document.documentElement.offsetHeight;
+}
+
+</script></head><body style="white-space: pre;" onload="boom();">
+H
+
+
+</body></html>
diff --git a/layout/base/crashtests/645572-1.html b/layout/base/crashtests/645572-1.html
new file mode 100644
index 0000000000..ee6618df74
--- /dev/null
+++ b/layout/base/crashtests/645572-1.html
@@ -0,0 +1,52 @@
+<html class="reftest-wait">
+<script>
+function start(){
+ tmp=document.createElement('iframe');
+ tmp.id='ifr32247';
+ tmp.addEventListener("load", start_dataiframe9);
+ document.documentElement.appendChild(tmp);
+}function start_dataiframe9(){
+ o185=document.getElementById('ifr32247').contentDocument.documentElement;
+ tmp=document.createElement('iframe')
+ o196=document.getElementById('ifr32247').contentDocument.createElementNS('http:2000svg','altGlyph');
+ o230=o185.cloneNode(true);
+ tmp.id='ifr42257';
+ tmp.addEventListener("load", start_dataiframe11);
+ o230.ownerDocument.documentElement.appendChild(tmp);
+ //window.setTimeout('start_dataiframe11()',100);
+}function start_dataiframe11(){
+ o232=o230.ownerDocument.getElementById('ifr42257').contentDocument.documentElement;
+ o234=o196;
+ tmp=o234.ownerDocument.createElement('iframe');
+ tmp.srcdoc="<q id='element2'><q id='element3'><q id='element4'><dd style id='element6'>";
+ tmp.id='ifr22371';
+ tmp.addEventListener("load", start_dataiframe12);
+ o234.ownerDocument.documentElement.appendChild(tmp);
+}function start_dataiframe12(){
+ o239=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element2');
+ o240=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element3');
+ o241=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element4');
+ o243=o234.ownerDocument.getElementById('ifr22371').contentDocument.getElementById('element6');
+ o232.addEventListener('DOMNodeRemoved',function(){this.offsetHeight;},true);
+ o272=o185.cloneNode(false);
+ o232.innerHTML=unescape("%3Cxmp%3E20style3E");
+ o276=document.createTextNode('window;');
+ o278=document.createTextNode('o243className=1;');
+ o243.innerHTML=unescape('22%3Cform%3E');
+ o232.appendChild(o241);
+ o288=o240.cloneNode(true);
+ o185.appendChild(o288);
+ o241.innerHTML='<input placeholder>';
+ o241.style.position='absolute';
+ o232.style.cssText='opacity:0;display:table;';
+ o241.appendChild(o276);
+ o241.appendChild(o239);
+ o241.offsetParent.appendChild(o243);
+ o288.appendChild(o272);
+ o240.appendChild(o276);
+ o241.offsetParent.appendChild(o278);
+ document.documentElement.removeAttribute("class");
+}
+addEventListener("load", start, false);
+</script>
+</html>
diff --git a/layout/base/crashtests/650475.xhtml b/layout/base/crashtests/650475.xhtml
new file mode 100644
index 0000000000..69d171b2da
--- /dev/null
+++ b/layout/base/crashtests/650475.xhtml
@@ -0,0 +1,14 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<script>
+
+function boom()
+{
+ document.body.offsetHeight;
+ document.body.appendChild(document.createTextNode('Y'));
+}
+
+</script>
+</head>
+<body style="white-space: pre;" onload="boom();">&#x000D;&#x064C;</body>
+</html>
diff --git a/layout/base/crashtests/650489.xhtml b/layout/base/crashtests/650489.xhtml
new file mode 100644
index 0000000000..64639619de
--- /dev/null
+++ b/layout/base/crashtests/650489.xhtml
@@ -0,0 +1,3 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="direction: rtl;"><body style="column-width: 1px; word-wrap: break-word; white-space: pre-wrap;" onload="document.documentElement.offsetHeight; document.body.style.wordWrap='';">
+
+xy</body></html>
diff --git a/layout/base/crashtests/651342-1.html b/layout/base/crashtests/651342-1.html
new file mode 100644
index 0000000000..a2851268b4
--- /dev/null
+++ b/layout/base/crashtests/651342-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html>
+<body style="position: relative; bottom: 2305843009213694000mozmm; float: left; border-bottom-style: solid;">r</body>
+</html>
diff --git a/layout/base/crashtests/653133-1.html b/layout/base/crashtests/653133-1.html
new file mode 100644
index 0000000000..d0de0585f5
--- /dev/null
+++ b/layout/base/crashtests/653133-1.html
@@ -0,0 +1,17 @@
+<html reftest-displayport-w="800" reftest-displayport-h="4096">
+<head>
+<style type="text/css">
+body
+{
+background-image:url("");
+background-attachment:fixed;
+}
+</style>
+</head>
+
+<body>
+<div style="height: 100000px">
+<h1>background-attachment:fixed crashtest</h1>
+</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/663295.html b/layout/base/crashtests/663295.html
new file mode 100644
index 0000000000..45cf350468
--- /dev/null
+++ b/layout/base/crashtests/663295.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html><html style="font-size-adjust: 193373343913878; white-space: pre-line;"><body onload="document.documentElement.style.columnGap='1px';"><span>A B C
+&#x062A;</span></body></html>
diff --git a/layout/base/crashtests/663662-1.html b/layout/base/crashtests/663662-1.html
new file mode 100644
index 0000000000..5bc45d9126
--- /dev/null
+++ b/layout/base/crashtests/663662-1.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head></head><body onload="document.documentElement.offsetHeight; document.body.style.columnWidth='40000px';" style="word-spacing: 200000px; font-size-adjust: 2000; direction: rtl; white-space: pre-wrap; width: 50000px; column-width: 1px; height: 5000px;"> &#x00A0;&#x000D;&#x0001;X&#x4372;Y </body></html>
diff --git a/layout/base/crashtests/663662-2.html b/layout/base/crashtests/663662-2.html
new file mode 100644
index 0000000000..9a87c975d3
--- /dev/null
+++ b/layout/base/crashtests/663662-2.html
@@ -0,0 +1 @@
+<!DOCTYPE html><html><head></head><body onload="document.documentElement.offsetHeight; document.body.style.columnWidth='40000px';" style="word-spacing: 200000px; font-size-adjust: 2000; direction: rtl; white-space: pre-wrap; width: 50000px; column-width: 1px; height: 5000px;"> &#x00A0;&#x000A;&#x0001;X&#x4372;Y </body></html>
diff --git a/layout/base/crashtests/665837.html b/layout/base/crashtests/665837.html
new file mode 100644
index 0000000000..666da2558a
--- /dev/null
+++ b/layout/base/crashtests/665837.html
@@ -0,0 +1,13 @@
+<html style="direction: rtl; column-width: 0pt; white-space: pre-line;"><head><script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.documentElement.style.fontSize = "200%";
+ document.documentElement.offsetHeight;
+}
+
+</script></head><body onload="boom();">
+
+A B
+C</body></html>
diff --git a/layout/base/crashtests/668579.html b/layout/base/crashtests/668579.html
new file mode 100644
index 0000000000..7a20129051
--- /dev/null
+++ b/layout/base/crashtests/668579.html
@@ -0,0 +1,10 @@
+<html><head></head><body>
+<script>
+document.body.setAttribute('style', 'position: fixed; transition-duration: 1s;transform: scale(1.5);');
+</script>
+</body>
+</html>
+
+
+
+
diff --git a/layout/base/crashtests/668941.xhtml b/layout/base/crashtests/668941.xhtml
new file mode 100644
index 0000000000..a1547a6b01
--- /dev/null
+++ b/layout/base/crashtests/668941.xhtml
@@ -0,0 +1,16 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="width: 1px; white-space: pre-wrap;">y
+<style>
+
+html:first-letter { }
+
+</style><script>
+
+window.addEventListener("load", function(){
+ document.documentElement.offsetHeight;
+ document.documentElement.style.direction = "rtl";
+ document.documentElement.offsetHeight;
+ document.documentElement.style.margin = "3em";
+ document.documentElement.offsetHeight;
+}, false);
+
+</script></html>
diff --git a/layout/base/crashtests/670226.html b/layout/base/crashtests/670226.html
new file mode 100644
index 0000000000..1207905200
--- /dev/null
+++ b/layout/base/crashtests/670226.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>body:first-letter { float: left; }</style>
+</head>
+
+<body style="white-space: pre-line;">&#x062A;
+</body>
+
+</html>
diff --git a/layout/base/crashtests/675246-1.xhtml b/layout/base/crashtests/675246-1.xhtml
new file mode 100644
index 0000000000..ded2e96e93
--- /dev/null
+++ b/layout/base/crashtests/675246-1.xhtml
@@ -0,0 +1,8 @@
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-paged">
+<style><![CDATA[
+ tfoot::after { content: "m"; position: fixed;}
+]]>
+</style>
+<td></td>
+<tfoot style="page-break-before: always;"></tfoot>
+</html>
diff --git a/layout/base/crashtests/690247-1.html b/layout/base/crashtests/690247-1.html
new file mode 100644
index 0000000000..8f9d9e96fe
--- /dev/null
+++ b/layout/base/crashtests/690247-1.html
@@ -0,0 +1,2 @@
+<html style="mask: url(&quot;#b&quot;);"><div style="overflow-x: scroll; overflow-y: scroll; font-size-adjust: 600"><math xmlns="http://www.w3.org/1998/Math/MathML"><mo>x</mo></math></div></html>
+
diff --git a/layout/base/crashtests/690619-1.html b/layout/base/crashtests/690619-1.html
new file mode 100644
index 0000000000..9b2c40641f
--- /dev/null
+++ b/layout/base/crashtests/690619-1.html
@@ -0,0 +1 @@
+<html style="background: -moz-element(#e);"><body><table><colgroup id="e"></colgroup></table></body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/691118-1.html b/layout/base/crashtests/691118-1.html
new file mode 100644
index 0000000000..9ce0aa9a62
--- /dev/null
+++ b/layout/base/crashtests/691118-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ document.getElementById("x").style.counterIncrement = "a";
+ document.documentElement.offsetHeight;
+}
+
+</script>
+
+<body onload="boom();" style="column-count: 3">
+ <div style="position: relative;">
+ <div style="position: absolute; height: 3pt;"></div>
+ <div style="position: absolute;" id="x"></div>
+ <div style="position: absolute; height: 8pt;"></div>
+ </div>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/695861.html b/layout/base/crashtests/695861.html
new file mode 100644
index 0000000000..af2323fd87
--- /dev/null
+++ b/layout/base/crashtests/695861.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.documentElement.offsetHeight; document.getElementById('s').style.textTransform='uppercase'; document.documentElement.offsetHeight; ">
+
+<div style="white-space: pre-wrap; column-count: 2;"><span id="s" style="unicode-bidi: isolate;">
+ <div style="direction: rtl;"></div></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/695964-1.svg b/layout/base/crashtests/695964-1.svg
new file mode 100644
index 0000000000..d6128ba761
--- /dev/null
+++ b/layout/base/crashtests/695964-1.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" style="transform-style: preserve-3d"><foreignObject/></svg>
diff --git a/layout/base/crashtests/698335.html b/layout/base/crashtests/698335.html
new file mode 100644
index 0000000000..c157e68e9a
--- /dev/null
+++ b/layout/base/crashtests/698335.html
@@ -0,0 +1,2 @@
+<!DOCTYPE html><html style="white-space: pre-wrap; direction: rtl; column-width: 1px;"><style style="display: none;">.fl:first-letter { }</style><body class="fl">&#xD288;&#x062A;
+D</body></html>
diff --git a/layout/base/crashtests/699353-1.html b/layout/base/crashtests/699353-1.html
new file mode 100644
index 0000000000..65e7251ab9
--- /dev/null
+++ b/layout/base/crashtests/699353-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+
+<script>
+
+function boom()
+{
+ document.execCommand("inserthtml", false, "ABC ");
+ document.execCommand("delete", false, null);
+ document.execCommand("inserthtml", false, "<style>");
+}
+
+</script>
+</head>
+
+<body onload="setTimeout(boom, 0);" contenteditable="true"></body>
+</html>
diff --git a/layout/base/crashtests/701504.html b/layout/base/crashtests/701504.html
new file mode 100644
index 0000000000..6bebc0fb0b
--- /dev/null
+++ b/layout/base/crashtests/701504.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+
+ var x = document.getElementById('x');
+ x.removeChild(x.childNodes[1]);
+
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();">
+
+<div style="column-count: 2;"><span style="unicode-bidi: isolate;" id="x"><span style="direction: rtl;"></span> <span style="unicode-bidi: isolate; white-space: pre;">
+x</span></span></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/707098.html b/layout/base/crashtests/707098.html
new file mode 100644
index 0000000000..3f89ee7fbc
--- /dev/null
+++ b/layout/base/crashtests/707098.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="var x = document.getElementById('x'); x.parentNode.removeChild(x);">
+<div><bdi><bdi><span id="x">&#x062A;</span> </bdi></bdi></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/709536-1.xhtml b/layout/base/crashtests/709536-1.xhtml
new file mode 100644
index 0000000000..934329b61d
--- /dev/null
+++ b/layout/base/crashtests/709536-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml" style="border-spacing: 300px; column-width: 0px;">h<body style="column-count: 1;"><td></td><textarea style="float: left;"></textarea></body></html>
diff --git a/layout/base/crashtests/722137.html b/layout/base/crashtests/722137.html
new file mode 100644
index 0000000000..7dae47f1de
--- /dev/null
+++ b/layout/base/crashtests/722137.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html style="width: 1px">
+<head>
+<script>
+
+function boom()
+{
+ document.documentElement.offsetHeight;
+ var x = document.getElementById("x").firstChild;
+ x.data = "a" + x.data;
+}
+
+</script>
+</head>
+
+<body onload="boom();"><span id="x">
+&#x202a;&#x10871;</span></body>
+</html>
diff --git a/layout/base/crashtests/725535.html b/layout/base/crashtests/725535.html
new file mode 100644
index 0000000000..92d64171d0
--- /dev/null
+++ b/layout/base/crashtests/725535.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html style="transform-style: preserve-3d">
+<body>
+<script>
+document.addEventListener("MozReftestInvalidate", function() {document.documentElement.style.transform = 'rotate(0)';});
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/727601.html b/layout/base/crashtests/727601.html
new file mode 100644
index 0000000000..f31d59e0c7
--- /dev/null
+++ b/layout/base/crashtests/727601.html
@@ -0,0 +1,3 @@
+<html style="display: table; transform: scalex(10);">
+<body><script>document.addEventListener("MozReftestInvalidate", function() {document.documentElement.style.transform = 'scalex(20)';})</script></body>
+</html>
diff --git a/layout/base/crashtests/735943.html b/layout/base/crashtests/735943.html
new file mode 100644
index 0000000000..d9e891302d
--- /dev/null
+++ b/layout/base/crashtests/735943.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<script>
+
+var asvg = '<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><script xlink:href="data:text/javascript," /><rect width="100" height="100" fill="green"><set attributeName="fill" attributeType="CSS" to="red" begin="0s" end="2s" dur="2s" fill="remove" /></rect></svg>';
+
+function boom()
+{
+ var f = document.createElementNS("http://www.w3.org/1999/xhtml", "iframe"); f.src = "data:text/html,1"; document.body.appendChild(f);
+ var w;
+
+ setTimeout(function() {
+ w = window.open("data:text/html,<body onload=window.close()>", "_blank", "width=200,height=200");
+ // Note that most of the code below will execute before the window appears, and in fact before "w" becomes non-null.
+ }, 0);
+
+ setTimeout(function() {
+ setTimeout(function() { }, 0);
+ f.contentWindow.location = "data:image/svg+xml," + encodeURIComponent(asvg);
+
+ setTimeout(function() {
+ setTimeout(function() {
+ setTimeout(function() {
+ document.body.style.columnCount = "2";
+ document.documentElement.className = "";
+ }, 20);
+ }, 0);
+ }, 0);
+ }, 20);
+}
+
+ window.addEventListener("MozReftestInvalidate", boom);
+</script>
+</head>
+
+<body></body>
+</html>
diff --git a/layout/base/crashtests/736389-1.xhtml b/layout/base/crashtests/736389-1.xhtml
new file mode 100644
index 0000000000..df8a6516c6
--- /dev/null
+++ b/layout/base/crashtests/736389-1.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<style>
+/* this stylesheet is reduced from quirk.css */
+
+li {
+ list-style-position: inside;
+}
+
+.t:first-child {
+ padding-top: 1em;
+}
+
+</style>
+
+<script>
+
+function rm(n) { n.parentNode.removeChild(n); }
+
+window.addEventListener("load", function() {
+ document.documentElement.offsetHeight;
+ rm(document.getElementById('x'));
+}, false);
+
+</script>
+</head>
+
+<body style="column-count: 2000;">
+ <li>
+ <ol class="t" style="position: relative;">
+ <span id="x"></span>
+ <ol class="t" style="list-style-position: inside;">
+ <div style="position: absolute;">
+ <li>
+ <div style="position: absolute;">
+ <li>
+ <ol class="t"></ol>
+ </li>
+ </div>
+ </li>
+ </div>
+ </ol>
+ </ol>
+ </li>
+</body>
+
+</html>
diff --git a/layout/base/crashtests/736924-1.html b/layout/base/crashtests/736924-1.html
new file mode 100644
index 0000000000..b9274bd78c
--- /dev/null
+++ b/layout/base/crashtests/736924-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<script>
+function boom()
+{
+ var a = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var b = document.createElementNS("http://www.w3.org/1999/xhtml", "span");
+ var x = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ var y = document.createElementNS("http://www.w3.org/1999/xhtml", "basefont");
+ var z = document.createElementNS("http://www.w3.org/1999/xhtml", "body");
+ z.setAttributeNS(null, "link", "#333333");
+
+ document.documentElement.appendChild(a);
+ b.appendChild(x);
+ b.appendChild(y);
+ document.documentElement.offsetHeight;
+ a.appendChild(b);
+ document.documentElement.offsetHeight;
+ document.createElementNS("http://www.w3.org/1999/xhtml", "div").appendChild(y);
+ b.appendChild(z);
+}
+</script>
+<body onload="boom();"></body>
diff --git a/layout/base/crashtests/749816-1.html b/layout/base/crashtests/749816-1.html
new file mode 100644
index 0000000000..125553886e
--- /dev/null
+++ b/layout/base/crashtests/749816-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+<title>crash in epoll_wait after changing display: table-column style to display:none on body</title>
+<script>
+function doe() {
+document.body.style.display = 'none';
+}
+setTimeout(doe, 1000);
+</script>
+</head>
+
+<body style="display: table-column;">
+This page should not crash Fennec
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/763223-1.html b/layout/base/crashtests/763223-1.html
new file mode 100644
index 0000000000..e970bb8aeb
--- /dev/null
+++ b/layout/base/crashtests/763223-1.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body style="position: relative; padding-right: 59729800px;" onload="document.documentElement.offsetHeight; document.getElementById('x').style.right = '100px';">
+<div id="x" style="position: absolute; width: -moz-fit-content; height: 3px;"></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/763702.xhtml b/layout/base/crashtests/763702.xhtml
new file mode 100644
index 0000000000..37e9fc5e6e
--- /dev/null
+++ b/layout/base/crashtests/763702.xhtml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Bug 763702 - crash in nsFontInflationData::FindFontInflationDataFor at crash address 0x28 (((nsIFrame*)0)->GetStateBits())</title>
+ </head>
+ <div>parseerror, this should not cause Fennec to crash
+
+</html>
diff --git a/layout/base/crashtests/767593-1.html b/layout/base/crashtests/767593-1.html
new file mode 100644
index 0000000000..1643cfd275
--- /dev/null
+++ b/layout/base/crashtests/767593-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<body>
+<div><span style="page-break-after: always;"></span><div style="position: fixed;"><span style="display: none;"></span></div>B</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/767593-2.html b/layout/base/crashtests/767593-2.html
new file mode 100644
index 0000000000..b612ba6b15
--- /dev/null
+++ b/layout/base/crashtests/767593-2.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html class="reftest-paged">
+<body>
+<div><span style="page-break-after: always;"></span><div style="position: fixed;"><span style="display: none;"></span><span style="display: none;"></span></div>B</div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/770381-1.html b/layout/base/crashtests/770381-1.html
new file mode 100644
index 0000000000..85528a81c5
--- /dev/null
+++ b/layout/base/crashtests/770381-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style id="s"></style>
+<script>
+function boom() { document.getElementById("s").textContent = "div { opacity: 0.5; }"; }
+</script>
+</head>
+<body onload="document.documentElement.offsetHeight; boom();">
+<div><div>X</div></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/772306.html b/layout/base/crashtests/772306.html
new file mode 100644
index 0000000000..396ad576de
--- /dev/null
+++ b/layout/base/crashtests/772306.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ var allNodes = [];
+ allNodes[5] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[5].style.setProperty("column-width", "200px", "");
+ allNodes[5].style.setProperty("height", "2em", "");
+ allNodes[7] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[7].style.setProperty("float", "left", "");
+ allNodes[30] = document.createElementNS("http://www.w3.org/1998/Math/MathML", "munder");
+ (allNodes[7] || allNodes[5] || document.body).appendChild(allNodes[30]);
+ (allNodes[5] || document.body).appendChild(allNodes[7]);
+ allNodes[17] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[17].style.setProperty("display", "inline-block", "");
+ (allNodes[5] || document.body).appendChild(allNodes[17]);
+ allNodes[20] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ (allNodes[5] || document.body).appendChild(allNodes[20]);
+ allNodes[23] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[23].style.setProperty("float", "left", "");
+ allNodes[25] = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ allNodes[25].style.setProperty("display", "inline-block", "");
+ (allNodes[23] || allNodes[5] || document.body).appendChild(allNodes[25]);
+ (allNodes[5] || document.body).appendChild(allNodes[23]);
+ (document.body).appendChild(allNodes[5]);
+ document.documentElement.offsetHeight;
+ allNodes[34] = document.createElementNS("http://www.w3.org/1998/Math/MathML", 'maligngroup');
+ allNodes[17].appendChild(allNodes[34]);
+ document.documentElement.offsetHeight;
+ allNodes[30].setAttribute('accentunder', "false");
+}
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/788360.html b/layout/base/crashtests/788360.html
new file mode 100644
index 0000000000..7a7766a4a8
--- /dev/null
+++ b/layout/base/crashtests/788360.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<body onload="document.documentElement.offsetHeight; document.getElementById('v').style.counterReset='chicken'; document.documentElement.offsetHeight;">
+
+<div style="backface-visibility: hidden; perspective: 12em; display: table;"><div style="column-count: 2; white-space: pre;" id="v">x<span style="float: right; display: inline-block; width: 24px; height: 24px; background: yellow;"></span></div></div>
+
+</body>
diff --git a/layout/base/crashtests/793848.html b/layout/base/crashtests/793848.html
new file mode 100644
index 0000000000..4f84a867c2
--- /dev/null
+++ b/layout/base/crashtests/793848.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+ function f(event) {
+ document.removeEventListener("DOMAttrModified", f);
+
+ // dumpln(event.attrChange); /* 2 (MutationEvent.ADDITION) */
+ // dumpln(event.attrName); /* "curpos" */
+ // dumpln(event.newValue); /* "0" */
+
+ // (gdb) break nsGlobalWindow::Dump
+ dump("[[[[DOMAttrModified\n");
+ document.removeChild(svgUse);
+ dump("]]]]\n");
+ }
+
+ var svgUse = document.createElementNS("http://www.w3.org/2000/svg", "use");
+ document.removeChild(document.documentElement);
+ document.addEventListener("DOMAttrModified", f);
+ document.appendChild(svgUse);
+
+</script>
+</head>
+
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/795646.html b/layout/base/crashtests/795646.html
new file mode 100644
index 0000000000..5ef210f115
--- /dev/null
+++ b/layout/base/crashtests/795646.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="position: fixed; backface-visibility: hidden;">
+<body onload="setTimeout(function() { document.documentElement.style.MozBackfaceVisibility = 'hidden'; }, 0);">
+<div style="position: fixed; height: 8px; width: 200px; background-color: yellow;"></div>
+V
+
+</body></html>
diff --git a/layout/base/crashtests/802902.html b/layout/base/crashtests/802902.html
new file mode 100644
index 0000000000..2f258206b6
--- /dev/null
+++ b/layout/base/crashtests/802902.html
@@ -0,0 +1,10 @@
+<div style=width:1;height:5000><script>
+document.onscroll=function(){alert("Scroll down as soon as you press ok!");}
+
+function initCF() {
+setTimeout("CFcrash()", 190);
+}
+document.addEventListener("DOMContentLoaded", initCF);
+function CFcrash() {
+try { window.scrollByLines(3); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/806056-1.html b/layout/base/crashtests/806056-1.html
new file mode 100644
index 0000000000..7472bac74a
--- /dev/null
+++ b/layout/base/crashtests/806056-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementsByTagName("td")[0].style.position = "absolute";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<table border=1><tbody><tr><td>X</td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/base/crashtests/806056-2.html b/layout/base/crashtests/806056-2.html
new file mode 100644
index 0000000000..c0fd20fece
--- /dev/null
+++ b/layout/base/crashtests/806056-2.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+
+function boom()
+{
+ document.getElementsByTagName("td")[0].style.position = "absolute";
+ document.body.getClientRects(); //flush
+ document.getElementsByTagName("tbody")[0].style.transformStyle = "preserve-3d";
+}
+
+</script>
+</head>
+<body onload="boom();">
+<table><tbody><tr><td></td></tr></tbody></table>
+</body>
+</html>
diff --git a/layout/base/crashtests/812665.html b/layout/base/crashtests/812665.html
new file mode 100644
index 0000000000..1d2edf11b4
--- /dev/null
+++ b/layout/base/crashtests/812665.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body onload="document.getElementById('x').style.transformStyle = '';">
+<div><span id="x" style="transform-style: preserve-3d;"><div><div style="position: fixed;"></div></div></span></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/813372-1.html b/layout/base/crashtests/813372-1.html
new file mode 100644
index 0000000000..254a9bbe75
--- /dev/null
+++ b/layout/base/crashtests/813372-1.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait" xmlns="http://www.w3.org/1999/xhtml">
+<head>
+<!-- There is, at present, no official xsd for (X)HTML5. A pity. Usefulness would depend on the parser and extensions made by the site. -->
+<title>testcase</title>
+ <style type="text/css">
+* { margin: 0; padding: 0; }
+.hide { top: 80% !important; width: 75% !important; height: 50% !important; }
+
+#details
+{
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 0%;
+ border: 10mm dotted red;
+ border-radius: 100em;
+ background-color: lime;
+ height: 0%;
+ overflow: scroll;
+ transition-property: top width;
+ transition-duration: 0.75s;
+ opacity: 0.9;
+}
+
+ </style>
+</head>
+<body>
+
+
+<section id="details" class="hide">
+I'm a test of hiding animation
+<button onclick="this.parentNode.classList.add('hide')">Click me to hide</button>
+</section>
+
+<script>
+var kNumIterations = 5;
+var currentIteration = 0;
+var inrval;
+
+function doe() {
+ if (++currentIteration >= kNumIterations) {
+ clearInterval(inrval);
+ document.documentElement.removeAttribute('class');
+ } else {
+ document.getElementById('details').classList.toggle('hide');
+ }
+}
+document.addEventListener("MozReftestInvalidate", function(){ inrval = setInterval(doe, 1000); });
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/817219-iframe.html b/layout/base/crashtests/817219-iframe.html
new file mode 100644
index 0000000000..5687873099
--- /dev/null
+++ b/layout/base/crashtests/817219-iframe.html
@@ -0,0 +1,35 @@
+<html>
+<script>
+function start() {
+o3=document.createElement('input');
+tmp = o3.ownerDocument.createElement('iframe');
+document.body.appendChild(tmp);
+o4=tmp.contentDocument;
+cb_3=function() { var f = callback_3; callback_3 = null; return f(arguments); }
+o3.addEventListener('change', cb_3);
+o51=document.createElement('img');
+o94=document.createElement('input');
+o94.type='checkbox';
+o3.appendChild(o94);
+o192=document.createElement('input');
+o192.type='button';
+o94.appendChild(o192);
+o263=document.createEvent('MouseEvents');
+o263.initMouseEvent('click', true, true, window,0, 0, 0, 0, 0, false, false, false, false, 0, null);
+o192.dispatchEvent(o263)
+}
+function callback_3() {
+o192.addEventListener('DOMNodeRemoved', callback_21, true);
+o51.appendChild(o192);
+}
+function callback_21() {
+o4.documentElement.appendChild(o192);
+location.reload();
+}
+</script>
+<body>
+<script>
+window.setTimeout("start();", 10);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/817219.html b/layout/base/crashtests/817219.html
new file mode 100644
index 0000000000..b474c229b2
--- /dev/null
+++ b/layout/base/crashtests/817219.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug 817219</title>
+<script>
+function reload() {
+ this.location.reload();
+}
+// Run the test for 2 seconds
+setTimeout(function() {
+ document.documentElement.removeChild(document.body);
+ document.documentElement.className = "";
+ }, 2000);
+</script>
+</head>
+<body onload="document.body.getBoundingClientRect()">
+
+<iframe onload="this.contentWindow.setTimeout(reload,1113)" src="817219-iframe.html"></iframe>
+<iframe onload="this.contentWindow.setTimeout(reload,1433)" src="817219-iframe.html"></iframe>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/818454.html b/layout/base/crashtests/818454.html
new file mode 100644
index 0000000000..66e04f8daa
--- /dev/null
+++ b/layout/base/crashtests/818454.html
@@ -0,0 +1,24 @@
+><pre>><style>#parent {
+ position: absolute;
+ }
+#parent::first-letter {
+</style>
+<video></video>>>><div id=parent>
+ <i> 9Z 1CU %b 1 *v
+` mMx#[j
+>></div>
+>><q><dt>><style>
+.class1 { stroke: none; direction: rtl;</style><script>
+var docElement = document.body;
+docElement.contentEditable = "true";
+function crash() {
+test1 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "degree");
+docElement.appendChild(test1);
+test2 = document.createElementNS("http://www.w3.org/1999/xhtml", "textarea");
+docElement.appendChild(test2);
+test3 = document.createElementNS("http://www.w3.org/1998/Math/MathML", "mover");
+test3.setAttribute("class", "class1");
+docElement.appendChild(test3);
+}
+document.addEventListener("DOMContentLoaded", crash);
+</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/822865.html b/layout/base/crashtests/822865.html
new file mode 100644
index 0000000000..86487bf96c
--- /dev/null
+++ b/layout/base/crashtests/822865.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<html style="-moz-appearance: radio; display: table;">
+<body onload="document.elementFromPoint(0, 0);"></body>
+</html>
diff --git a/layout/base/crashtests/824300.html b/layout/base/crashtests/824300.html
new file mode 100644
index 0000000000..e23d008583
--- /dev/null
+++ b/layout/base/crashtests/824300.html
@@ -0,0 +1,13 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+
+ <span style="filter: url(#f);">
+ <div style="transform: matrix(2, 1, 0, 2, 50, 50);"></div>
+ </span>
+
+ <svg xmlns="http://www.w3.org/2000/svg">
+ <filter id="f"/>
+ </svg>
+
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/crashtests/824862.html b/layout/base/crashtests/824862.html
new file mode 100644
index 0000000000..9beb060880
--- /dev/null
+++ b/layout/base/crashtests/824862.html
@@ -0,0 +1,5 @@
+<style>.error:before {
+ content: counter(c, none) "z";
+ display: flex;
+</style>
+><body style="overflow-x: clip; ">><div class=error> \ No newline at end of file
diff --git a/layout/base/crashtests/826163.html b/layout/base/crashtests/826163.html
new file mode 100644
index 0000000000..3fcf1ab0e4
--- /dev/null
+++ b/layout/base/crashtests/826163.html
@@ -0,0 +1,11 @@
+<cell id=test1>h A</cell>>!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!<card>>>>>
+zq
+^I~
+U5=m 9l( 5 n 3
+=o~
+i 0 U]C`EE# RH%o9)&amp;` |: Z {Q-4 `.b^,G /7
+<body dir=rtl>>><script>
+document.addEventListener("DOMContentLoaded", CFcrash);
+function CFcrash() {
+try { document.implementation.createDocument("", "", null).adoptNode(test1); } catch(e) {}
+}</script>> \ No newline at end of file
diff --git a/layout/base/crashtests/827192.html b/layout/base/crashtests/827192.html
new file mode 100644
index 0000000000..458fdad743
--- /dev/null
+++ b/layout/base/crashtests/827192.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html dir="rtl" style="column-count: 2; padding-left: 40000px;">
+<body onload="document.getElementById('x').style.cssFloat = '';">
+
+A<br id="x" style="float: right;">
+B
+
+</body>
+</html>
diff --git a/layout/base/crashtests/830138-1.html b/layout/base/crashtests/830138-1.html
new file mode 100644
index 0000000000..b1c41b2647
--- /dev/null
+++ b/layout/base/crashtests/830138-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<body>
+<math><menclose id="m" style="transform:translate(10px,0)">
+ <ll style="display:block">
+ <ll id=test2 style="display:none; position: fixed"></ll>
+ </ll>
+</menclose></math>
+<script>
+function doTest() {
+ document.getElementById("test2").setAttribute("style", "position:fixed")
+ document.documentElement.removeAttribute("class");
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/830192-1.html b/layout/base/crashtests/830192-1.html
new file mode 100644
index 0000000000..833cd796a2
--- /dev/null
+++ b/layout/base/crashtests/830192-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+.test {
+ position:fixed;
+ display:none;
+ width:100px; height:100px;
+ background:yellow;
+}
+.doTest .test {
+ display:block;
+}
+</style>
+</head>
+<body>
+<table>
+<tr style="transform:translate(10px,0)">
+<td>
+ <div class="test"></div>
+</td>
+</tr>
+</table>
+<script>
+function doTest() {
+ document.documentElement.setAttribute("class", "doTest");
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/830299-1.html b/layout/base/crashtests/830299-1.html
new file mode 100644
index 0000000000..34286661b4
--- /dev/null
+++ b/layout/base/crashtests/830299-1.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<style>
+.test {
+ position:fixed;
+ display:none;
+ width:100px; height:100px;
+ background:yellow;
+}
+.doTest .test {
+ display:block;
+}
+</style>
+</head>
+<body>
+<div style="transform:translate(10px,0); overflow:scroll; width:200px; height:200px;">
+ <div class="test"></div>
+</div>
+<script>
+function doTest() {
+ document.documentElement.setAttribute("class", "doTest");
+}
+window.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/833604-1.html b/layout/base/crashtests/833604-1.html
new file mode 100644
index 0000000000..934860bf97
--- /dev/null
+++ b/layout/base/crashtests/833604-1.html
@@ -0,0 +1,18 @@
+<html>
+<script>
+function start() {
+try{o0=document.body;}catch(e){}
+try{o11=document.createElement('input');;}catch(e){}
+try{o0.appendChild(o11);}catch(e){}
+try{document.documentElement.offsetHeight;}catch(e){}
+try{o0.style.cssText = 'transform: matrix(1, -0.2, 0, 1, 0, 0);'}catch(e){}
+try{o11.style.position='fixed';}catch(e){}
+window.setTimeout('window.start_waitfor0()',10);
+}
+function start_waitfor0() {
+try{o0.style.display='table-column';}catch(e){}
+try{o11.offsetHeight;}catch(e){}
+}
+</script>
+<body onload="start()"></body>
+</html>
diff --git a/layout/base/crashtests/835056.html b/layout/base/crashtests/835056.html
new file mode 100644
index 0000000000..874b97a356
--- /dev/null
+++ b/layout/base/crashtests/835056.html
@@ -0,0 +1,19 @@
+<html>
+<head>
+<style type="text/css">
+html, body
+{
+ overflow: hidden;
+}
+
+body
+{
+ backface-visibility: hidden;
+}
+</style>
+</head>
+<body>
+<div style="position: fixed"></div>
+</body>
+</html>
+
diff --git a/layout/base/crashtests/836990-1.html b/layout/base/crashtests/836990-1.html
new file mode 100644
index 0000000000..d81331467f
--- /dev/null
+++ b/layout/base/crashtests/836990-1.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="overflow: auto; transform:translate(100px,0);">
+ <div style="position: relative;"><div id="x" style="position:fixed; display:none"></div></div>
+</div>
+<script>
+document.body.getBoundingClientRect();
+document.getElementById('x').style.display = '';
+</script>
+</body>
+</html>
diff --git a/layout/base/crashtests/840480.html b/layout/base/crashtests/840480.html
new file mode 100644
index 0000000000..7ef5944d58
--- /dev/null
+++ b/layout/base/crashtests/840480.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<HTML>
+<BODY>
+<CENTER ID="Test-CENTER">
+</CENTER>
+<BR />
+<B ID="Test-B" ></B>
+<BR />
+<DL>
+ <DT ID="Test-DT" CLASS="DT-class"></DT>
+</DL>
+<SPAN ID="Test-SPAN" CLASS="SPAN-class"></SPAN>
+<DFN ID="Test-DFN" >
+ <VAR ID="Test-VAR"></VAR>
+ <CITE ID="Test-CITE">Boom</CITE>
+</DFN>
+<ABBR ID="Test-ABBR" ></ABBR>
+<script type="text/javascript">
+
+ document.head.appendChild(document.createElement("style"));
+var styleSheet0 = document.styleSheets[0];
+
+var test0=document.getElementById("Test-DT")
+var test4=document.getElementById("Test-DFN")
+var test5=document.getElementById("Test-CENTER")
+var test7=document.getElementById("Test-B")
+var test18=document.getElementById("Test-ABBR")
+var test19=document.getElementById("Test-SPAN")
+
+for(x=0;x<14;x++){
+ test18.appendChild(test5.cloneNode(true));
+test18.appendChild(test7);
+test19.appendChild(test4.cloneNode(true));
+}
+
+styleSheet0.insertRule('.U-class,.DT-class,.SPAN-class,.I-class{display: table-caption; content: counter(c, binary); counter-increment:c;}',0);
+window.scrollTo(688,835)
+styleSheet0.insertRule('#Test-SPAN,#Test-NOFRAMES,#Test-CITE,#Test-EM{list-style-type:upper-roman; transition-property:none; transform:rotate(-90deg) translate(-2em, -18em); background-clip:border-box; border-collapse:collapsed; }',0);
+test7.style.setProperty('overflow','hidden','important');
+test7.appendChild(test0.cloneNode(true));
+</script>
+
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/842114.html b/layout/base/crashtests/842114.html
new file mode 100644
index 0000000000..18af6aca95
--- /dev/null
+++ b/layout/base/crashtests/842114.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+<body>
+<div style="filter: url(#f); display: inline;"><div></div><svg><filter id="f"/></svg></div>
+</body>
+</html>
diff --git a/layout/base/crashtests/847242.html b/layout/base/crashtests/847242.html
new file mode 100644
index 0000000000..c148dbb663
--- /dev/null
+++ b/layout/base/crashtests/847242.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+<head>
+<style>
+.f { unicode-bidi: bidi-override; width: 1px; white-space: pre-line; }
+.f:first-letter { font-size: 200% }
+</style>
+</head>
+<body>
+<div class="f">&#x2029;&#x062a;&#x8401;
+x</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/852293.html b/layout/base/crashtests/852293.html
new file mode 100644
index 0000000000..8c4783933d
--- /dev/null
+++ b/layout/base/crashtests/852293.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="UTF-8">
+<script>
+"use strict";
+
+var i = 0;
+var x;
+var fixedDiv;
+var sheet;
+
+function start()
+{
+ clearChildren(document.documentElement);
+
+ for (var j = 0; j < 10; ++j) {
+ document.documentElement.appendChild(document.createElement("div"));
+ }
+ x = document.getElementsByTagName("div")[0];
+
+ fixedDiv = document.createElementNS("http://www.w3.org/1999/xhtml", "div");
+ fixedDiv.style.setProperty("position", "fixed", "");
+
+ setTimeout(makeChanges, 10);
+}
+
+function makeChanges()
+{
+ ++i;
+ x.appendChild(fixedDiv);
+ sheet = document.createElement("style");
+ sheet.appendChild(document.createTextNode("* { transform: matrix(1, 2, 3, 4, 5, 6); }"));
+ document.documentElement.appendChild(sheet);
+ if (i >= 200) {
+ document.documentElement.removeAttribute("class");
+ return;
+ }
+ setTimeout(revertChanges, 10);
+}
+
+function revertChanges()
+{
+ x.removeChild(fixedDiv);
+ document.documentElement.removeChild(sheet);
+ bounceDE();
+ setTimeout(makeChanges, 10);
+}
+
+
+function bounceDE()
+{
+ var de = document.documentElement;
+ document.removeChild(de);
+ document.appendChild(de);
+}
+
+function clearChildren(root)
+{
+ while(root.firstChild) { root.firstChild.remove(); }
+}
+
+</script>
+</head>
+
+<body onload="start();"></body>
+</html>
diff --git a/layout/base/crashtests/859526-1.html b/layout/base/crashtests/859526-1.html
new file mode 100644
index 0000000000..9e2574fd7f
--- /dev/null
+++ b/layout/base/crashtests/859526-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="transform-style: preserve-3d;"><head>
+<meta http-equiv="content-type" content="text/html; charset=windows-1252"></head><body>
+<iframe></iframe>
+
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/859630-1.html b/layout/base/crashtests/859630-1.html
new file mode 100644
index 0000000000..ca0cd9df85
--- /dev/null
+++ b/layout/base/crashtests/859630-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html><head>
+<meta http-equiv="content-type" content="text/html; charset=UTF-8"><meta charset="UTF-8">
+</head><body>
+<div style="display: table-caption"><iframe></iframe></div>
+
+</body></html> \ No newline at end of file
diff --git a/layout/base/crashtests/860579-1.html b/layout/base/crashtests/860579-1.html
new file mode 100644
index 0000000000..3f7ef558b2
--- /dev/null
+++ b/layout/base/crashtests/860579-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script>
+function addFrame(contents)
+{
+ var frame = document.createElement("iframe");
+ frame.src = "data:text/html," + contents;
+ document.body.appendChild(frame);
+}
+function boom()
+{
+ addFrame("1");
+ document.documentElement.offsetHeight;
+ addFrame("2");
+ document.body.style.display = "table-caption";
+}
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/866588.html b/layout/base/crashtests/866588.html
new file mode 100644
index 0000000000..4e9abfdd52
--- /dev/null
+++ b/layout/base/crashtests/866588.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+body { white-space: pre-wrap; width: 1ch; font-family: monospace }
+body:first-line { }
+
+</style>
+
+<script>
+
+function boom()
+{
+ document.body.textContent = "\n\u202AX ";
+ document.documentElement.offsetHeight;
+ document.body.appendChild(document.createTextNode("Y"));
+ document.documentElement.offsetHeight;
+}
+
+</script>
+</head>
+<body onload="boom();"></body>
+</html>
diff --git a/layout/base/crashtests/876092.html b/layout/base/crashtests/876092.html
new file mode 100644
index 0000000000..b9b558ff30
--- /dev/null
+++ b/layout/base/crashtests/876092.html
@@ -0,0 +1,29 @@
+<html>
+ <head>
+ <style>
+ #s2::before {
+ content: "a";
+ position: absolute;
+ }
+ #s1 {
+ overflow: clip;
+ }
+ #s3 {
+ position: relative;
+ }
+ #s5 {
+ position: absolute;
+ }
+ </style>
+ </head>
+ <body>
+ <strike id="s1">
+ <strike id="s2">
+ <small id="s3">
+ <div>
+ <div id="s4"></div>
+ </strike>
+ </div>
+ </body>
+</html>
+
diff --git a/layout/base/crashtests/876221.html b/layout/base/crashtests/876221.html
new file mode 100644
index 0000000000..ad3cc30384
--- /dev/null
+++ b/layout/base/crashtests/876221.html
@@ -0,0 +1,39 @@
+<html>
+<script>
+function start() {
+o0=tmp = document.createElement('iframe');
+document.getElementById('store_div').appendChild(tmp);
+o19=document.documentElement;
+tmp.id = 'id28'
+o119=tmp = document.createElement('iframe');
+tmp.id = 'id63'
+o19.appendChild(tmp)
+o152=document.getElementById('id63').contentDocument;
+o515=o152.createElement('xml');
+o547=document.createElementNS('http://www.w3.org/1999/xhtml','feFuncB');
+o552=document.createElementNS('http://www.w3.org/1999/xhtml','munder');
+o569=window.document.getElementById('id28').contentWindow.document;
+document.body.appendChild(o552);
+o552.appendChild(o547);
+o547.appendChild(o515);
+o582=o569.createElement('dl');
+o588=document.createElement('input');
+o552.style.cssText = 'overflow: clip; '
+o552.style.position='absolute';
+o600=o515.offsetParent;
+o619=document.createElement('input');
+o635=o569.createElement('input');
+o635.type='image';
+o600.appendChild(o635);
+o588.style.position='absolute';
+o635.appendChild(o582);
+o588.appendChild(o619);
+o670=o619.parentNode;
+o552.style.position=null;
+o582.appendChild(o670);
+o635.style.position='relative';
+}
+</script>
+<body onload="start()"><div id="store_div"></div></body>
+</html>
+
diff --git a/layout/base/crashtests/89101-1.html b/layout/base/crashtests/89101-1.html
new file mode 100644
index 0000000000..09ce185244
--- /dev/null
+++ b/layout/base/crashtests/89101-1.html
@@ -0,0 +1,22 @@
+<HTML>
+<FORM>
+
+<fieldset STYLE="
+
+ position:fixed;
+ left:
+ 311;
+ top:
+ 248;
+ width:
+ 371;
+
+ height:
+ 184;
+
+
+ ">
+<input TYPE="text" NAME="Sub1104001010" VALUE="" TABINDEX="11" MAXLENGTH="10">
+</FIELDSET>
+</FORM>
+
diff --git a/layout/base/crashtests/89358-1.html b/layout/base/crashtests/89358-1.html
new file mode 100644
index 0000000000..39702f7beb
--- /dev/null
+++ b/layout/base/crashtests/89358-1.html
@@ -0,0 +1,10 @@
+<HTML>
+<HEAD>
+<META HTTP-EQUIV="Content-Type" CONTENT="text/html , charset=x-user-defined">
+</HEAD>
+<BODY>
+<PRE>
+<A HREF="http://www.test.net/">http://www.test.net </A> Mozilla-0.9.2 is dying - blah.!?
+</PRE>
+</BODY>
+</HTML>
diff --git a/layout/base/crashtests/897852.html b/layout/base/crashtests/897852.html
new file mode 100644
index 0000000000..a7fe1437b4
--- /dev/null
+++ b/layout/base/crashtests/897852.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: table-column;">
+<iframe src="data:text/html,<html contenteditable=''><script>var f = window.frameElement; window.addEventListener('load', function() { window.addEventListener('DOMNodeInserted', function() { f.parentNode.removeChild(f); }, true); f.parentNode.style.cssFloat = 'right'; }, false);</script>";"
+</body>
+</html>
diff --git a/layout/base/crashtests/898913.html b/layout/base/crashtests/898913.html
new file mode 100644
index 0000000000..f81602a111
--- /dev/null
+++ b/layout/base/crashtests/898913.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+
+div { page-break-inside: avoid; }
+div:first-letter { float: right; }
+
+</style>
+<script>
+
+function boom()
+{
+ var d = document.getElementById('d');
+ d.firstChild.remove();
+}
+
+</script>
+</head>
+<body onload="boom();">
+<div id="d">&#x202B;</div>
+</body>
+</html>
diff --git a/layout/base/crashtests/90205-1.html b/layout/base/crashtests/90205-1.html
new file mode 100644
index 0000000000..0092c56802
--- /dev/null
+++ b/layout/base/crashtests/90205-1.html
@@ -0,0 +1,15 @@
+<html>
+<head>
+ <title>Bug 90205</title>
+</head>
+<body>
+ <span style="font-family: serif;">
+ <span style="float: left;"></span>
+ </span>
+ <font size=2>
+ <meta>
+ <form></form>
+ </font>
+ <body topmargin="0">
+</body>
+</html>
diff --git a/layout/base/crashtests/926728.html b/layout/base/crashtests/926728.html
new file mode 100644
index 0000000000..85883f0fec
--- /dev/null
+++ b/layout/base/crashtests/926728.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <span id="x" style="position: sticky; bottom: 75px;">
+ <div></div>
+ </span>
+ <script>
+ document.addEventListener("MozReftestInvalidate", function() {
+ document.getElementById('x').style.bottom = '-3000px';
+ });
+ </script>
+ </body>
+</html>
diff --git a/layout/base/crashtests/930381.html b/layout/base/crashtests/930381.html
new file mode 100644
index 0000000000..9d11a37a84
--- /dev/null
+++ b/layout/base/crashtests/930381.html
@@ -0,0 +1,122 @@
+<script>
+function fuzz(){
+ var a=document.getElementById('a');
+ var b=document.getElementById('b');
+ var pa=a.parentNode;
+ b.parentNode.replaceChild(a,b);
+ pa.appendChild(b);
+}
+</script>
+<big>
+<menu>
+<address>
+<optgroup label="a"></optgroup>
+"
+<blockquote>
+a
+<ruby>a</ruby>
+</address>
+<s dir="rtl">
+<section>
+<fieldset id="a"><iframe></iframe></fieldset>
+</section>
+<body onmouseover="fuzz()">
+<video id="b">
+
+<!--
+==21242==ERROR: AddressSanitizer: heap-use-after-free on address 0x61700022a21c at pc 0x7f0fe52bd9bc bp 0x7fff20ff6650 sp 0x7fff20ff6648
+READ of size 4 at 0x61700022a21c thread T0
+ #0 0x7f0fe52bd9bb (libxul.so!PresShell::DispatchSynthMouseMove(mozilla::WidgetGUIEvent*, bool)+0x1db)
+ Line 75 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/RestyleManager.h"
+ #1 0x7f0fe52cc0c4 (libxul.so!PresShell::ProcessSynthMouseMoveEvent(bool)+0xde4)
+ Line 5256 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsPresShell.cpp"
+ #2 0x7f0fe52f0547 (libxul.so!nsRefreshDriver::Tick(long, mozilla::TimeStamp)+0xbb7)
+ Line 1074 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsRefreshDriver.cpp"
+ #3 0x7f0fe52f64e0 (libxul.so!mozilla::RefreshDriverTimer::Tick()+0x1f0)
+ Line 168 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsRefreshDriver.cpp"
+ #4 0x7f0fe8de4c31 (libxul.so!nsTimerImpl::Fire()+0x6d1)
+ Line 546 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsTimerImpl.cpp"
+ #5 0x7f0fe8de52d6 (libxul.so!nsTimerEvent::Run()+0x66)
+ Line 630 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsTimerImpl.cpp"
+ #6 0x7f0fe8ddc019 (libxul.so!nsThread::ProcessNextEvent(bool, bool*)+0xaa9)
+ Line 622 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/threads/nsThread.cpp"
+ #7 0x7f0fe8d08371 (libxul.so!NS_ProcessNextEvent(nsIThread*, bool)+0xb1)
+ Line 251 of "/builds/slave/m-in-l64-asan-0000000000000000/build/xpcom/glue/nsThreadUtils.cpp"
+ #8 0x7f0fe7955091 (libxul.so!mozilla::ipc::MessagePump::Run(base::MessagePump::Delegate*)+0x311)
+ Line 85 of "/builds/slave/m-in-l64-asan-0000000000000000/build/ipc/glue/MessagePump.cpp"
+ #9 0x7f0fe8ef7653 (libxul.so!MessageLoop::Run()+0x1c3)
+ Line 220 of "/builds/slave/m-in-l64-asan-0000000000000000/build/ipc/chromium/src/base/message_loop.cc"
+ #10 0x7f0fe7733cac (libxul.so!nsBaseAppShell::Run()+0x5c)
+ Line 161 of "/builds/slave/m-in-l64-asan-0000000000000000/build/widget/xpwidgets/nsBaseAppShell.cpp"
+ #11 0x7f0fe7135d9e (libxul.so!nsAppStartup::Run()+0xbe)
+ Line 268 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/components/startup/nsAppStartup.cpp"
+ #12 0x7f0fe46bf1c5 (libxul.so!XREMain::XRE_mainRun()+0x1e05)
+ Line 3886 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #13 0x7f0fe46c00fa (libxul.so!XREMain::XRE_main(int, char**, nsXREAppData const*)+0x4fa)
+ Line 3954 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #14 0x7f0fe46c102b (libxul.so!XRE_main+0x3ab)
+ Line 4156 of "/builds/slave/m-in-l64-asan-0000000000000000/build/toolkit/xre/nsAppRunner.cpp"
+ #15 0x459d1d (firefox!main+0x94d)
+ Line 275 of "/builds/slave/m-in-l64-asan-0000000000000000/build/browser/app/nsBrowserApp.cpp"
+ #16 0x7f0ff3d5876c (libc.so.6!__libc_start_main+0xec)
+ Line 226 of "libc-start.c"
+ #17 0x45929c (firefox!_start+0x28)
+0x61700022a21c is located 28 bytes inside of 760-byte region [0x61700022a200,0x61700022a4f8)
+freed by thread T0 here:
+ #0 0x4461a5 (firefox!free+0x55)
+ Line 64 of "/builds/slave/moz-toolchain/src/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc"
+ #1 0x7f0fe529f118 (libxul.so!mozilla::RestyleManager::Release()+0x138)
+ Line 225 of "../../dist/include/mozilla/mozalloc.h"
+previously allocated by thread T0 here:
+ #0 0x4462e5 (firefox!malloc+0x55)
+ Line 74 of "/builds/slave/moz-toolchain/src/llvm/projects/compiler-rt/lib/asan/asan_malloc_linux.cc"
+ #1 0x7f0feddfe5c8 (libmozalloc.so!moz_xmalloc+0x8)
+ Line 54 of "/builds/slave/m-in-l64-asan-0000000000000000/build/memory/mozalloc/mozalloc.cpp"
+ #2 0x7f0fe5230421 (libxul.so!nsDocumentViewer::InitInternal(nsIWidget*, nsISupports*, nsIntRect const&, bool, bool, bool)+0x581)
+ Line 824 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsDocumentViewer.cpp"
+ #3 0x7f0fe522fe90 (libxul.so!nsDocumentViewer::Init(nsIWidget*, nsIntRect const&)+0x20)
+ Line 642 of "/builds/slave/m-in-l64-asan-0000000000000000/build/layout/base/nsDocumentViewer.cpp"
+ #4 0x7f0fe929f537 (libxul.so!nsDocShell::Embed(nsIDocumentViewer*, char const*, nsISupports*)+0xe7)
+ Line 6397 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDocShell.cpp"
+ #5 0x7f0fe92b14f4 (libxul.so!nsDocShell::CreateDocumentViewer(char const*, nsIRequest*, nsIStreamListener**)+0x1084)
+ Line 8173 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDocShell.cpp"
+ #6 0x7f0fe9254ad4 (libxul.so!nsDSURIContentListener::DoContent(char const*, bool, nsIRequest*, nsIStreamListener**, bool*)+0x304)
+ Line 122 of "/builds/slave/m-in-l64-asan-0000000000000000/build/docshell/base/nsDSURIContentListener.cpp"
+ #7 0x7f0fe92f698f (libxul.so!nsDocumentOpenInfo::TryContentListener(nsIURIContentListener*, nsIChannel*)+0x6ef)
+ Line 680 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #8 0x7f0fe92f433c (libxul.so!nsDocumentOpenInfo::DispatchContent(nsIRequest*, nsISupports*)+0x67c)
+ Line 382 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #9 0x7f0fe92f3aaf (libxul.so!nsDocumentOpenInfo::OnStartRequest(nsIRequest*, nsISupports*)+0x32f)
+ Line 258 of "/builds/slave/m-in-l64-asan-0000000000000000/build/uriloader/base/nsURILoader.cpp"
+ #10 0x7f0fe4964bc2 (libxul.so!nsBaseChannel::OnStartRequest(nsIRequest*, nsISupports*)+0x1e2)
+ Line 718 of "/builds/slave/m-in-l64-asan-0000000000000000/build/netwerk/base/src/nsBaseChannel.cpp"
+Shadow bytes around the buggy address:
+ 0x0c2e8003d3f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d400: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d410: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
+ 0x0c2e8003d420: 00 fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
+ 0x0c2e8003d430: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
+=>0x0c2e8003d440: fd fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d450: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d460: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d470: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d480: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
+ 0x0c2e8003d490: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
+Shadow byte legend (one shadow byte represents 8 application bytes):
+ Addressable: 00
+ Partially addressable: 01 02 03 04 05 06 07
+ Heap left redzone: fa
+ Heap right redzone: fb
+ Freed heap region: fd
+ Stack left redzone: f1
+ Stack mid redzone: f2
+ Stack right redzone: f3
+ Stack partial redzone: f4
+ Stack after return: f5
+ Stack use after scope: f8
+ Global redzone: f9
+ Global init order: f6
+ Poisoned by user: f7
+ ASan internal: fe
+==21242==ABORTING
+-->
diff --git a/layout/base/crashtests/931450.html b/layout/base/crashtests/931450.html
new file mode 100644
index 0000000000..fa8dfd59e0
--- /dev/null
+++ b/layout/base/crashtests/931450.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html dir="rtl">
+<head>
+<meta charset="UTF-8">
+<body>
+
+<div style="position: fixed;"><p style="overflow-y: hidden;">A<span style="position: sticky;">B$</span></p></div>
+
+</body>
+</html>
diff --git a/layout/base/crashtests/931460-1.html b/layout/base/crashtests/931460-1.html
new file mode 100644
index 0000000000..812cd9b38a
--- /dev/null
+++ b/layout/base/crashtests/931460-1.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<html>
+<meta charset="UTF-8">
+<body><fieldset style="overflow: hidden;"><legend style="position: sticky;"></legend></fieldset></body>
+</html>
diff --git a/layout/base/crashtests/931464.html b/layout/base/crashtests/931464.html
new file mode 100644
index 0000000000..6370203312
--- /dev/null
+++ b/layout/base/crashtests/931464.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+
+function boom()
+{
+ var fieldset = document.getElementById("f");
+ for (var i = 0; i < 2; ++i)
+ fieldset.appendChild(document.createElement("span"));
+}
+
+</script>
+<body onload="boom();">
+<fieldset id="f" style="overflow: auto;"></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/935765-1.html b/layout/base/crashtests/935765-1.html
new file mode 100644
index 0000000000..c30f492fb1
--- /dev/null
+++ b/layout/base/crashtests/935765-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="document.getElementById('a').remove();">
+<fieldset style="overflow: scroll;"><legend><textarea id="a" style="position: sticky;"></textarea></legend></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/936988-1.html b/layout/base/crashtests/936988-1.html
new file mode 100644
index 0000000000..062125e355
--- /dev/null
+++ b/layout/base/crashtests/936988-1.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+</head>
+<body onload="document.getElementById('f').appendChild(document.createTextNode('X'));">
+<fieldset id="f"><legend style="display: table-row-group;"></legend></fieldset>
+</body>
+</html>
diff --git a/layout/base/crashtests/942690.html b/layout/base/crashtests/942690.html
new file mode 100644
index 0000000000..da64dd00dc
--- /dev/null
+++ b/layout/base/crashtests/942690.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=UTF-8">
+ <title>14 Rendering — HTML Standard</title>
+ <style>
+ pre.css:first-line { color: #AAAA50; }
+ </style>
+ </head>
+<body>
+ <pre class="css">foo
+
+bar ׳
+</pre>
+</body></html>
diff --git a/layout/base/crashtests/973390-1.html b/layout/base/crashtests/973390-1.html
new file mode 100644
index 0000000000..89e6c2694c
--- /dev/null
+++ b/layout/base/crashtests/973390-1.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html style="display: flex;">
+<head>
+<meta charset="UTF-8">
+</head>
+<body style="display: table-cell;"></body>
+</html>
diff --git a/layout/base/crashtests/989994-1.html b/layout/base/crashtests/989994-1.html
new file mode 100644
index 0000000000..a2ef1d472c
--- /dev/null
+++ b/layout/base/crashtests/989994-1.html
@@ -0,0 +1,5 @@
+<style>div:first-letter {
+</style>
+><mo style="white-space: pre-line; "><body style="flex: 31 125 auto; text-align: justify; " onload="document.getElementById('s').style.height = '700px';"><div><csactions> &#xb29b;
+ &#x7051;
+/&#xaa72;<span dir=rtl id=s var-\ffffff: rgb(107, > \ No newline at end of file
diff --git a/layout/base/crashtests/99776-1.html b/layout/base/crashtests/99776-1.html
new file mode 100644
index 0000000000..0ce2fcb761
--- /dev/null
+++ b/layout/base/crashtests/99776-1.html
@@ -0,0 +1,9 @@
+
+
+
+<html><head><title>Testcase for bug 99776</title></head>
+<body>
+
+<applet style="position:absolute;left:7;top:73;"></applet>
+
+</body></html>
diff --git a/layout/base/crashtests/crashtests.list b/layout/base/crashtests/crashtests.list
new file mode 100644
index 0000000000..44572dad53
--- /dev/null
+++ b/layout/base/crashtests/crashtests.list
@@ -0,0 +1,579 @@
+load 46043-1.html
+load 47843-1.html
+load 49122-1.html
+load 50257-1.html
+load 50395-1.html
+load 56746-1.html
+load 89101-1.html
+load 89358-1.html
+load 90205-1.html
+skip-if(cocoaWidget&&browserIsRemote) load 99776-1.html # Bug 849747
+load 118931-1.html
+load 121533-1.html
+load 123049-1.html
+load 123946-1.html
+load 128855-1.html
+load 133410-1.html
+load 143862-1a.html
+load 143862-1b.html
+load 143862-1c.html
+load 143862-2.html
+load 147320-1.html
+load 148245-1.html
+load 149014-1.html
+load 150431-1.html
+load 176915-1.html
+load 191272-1.html
+load 199696-1.html
+load 217903-1.html
+load 223064-1.html
+load 234851-1.html
+load 234851-2.html
+load 241300-1.html
+load 243159-1.html
+load 243159-2.xhtml
+load 243519-1.html
+load 244490-1.html
+load 254367-1.html
+load 263359-1.html
+load 265027-1.html
+load 265736-1.html
+load 265736-2.html
+load 265899-1.html
+load 265973-1.html
+load 265986-1.html
+load 265999-1.html
+load 266222-1.html
+asserts(1-7) load 266360-1.html # bug 576358
+load 266445-1.html
+load 266445-2.html
+load 268157-1.html
+load 269566-1.html
+load 272647-1.html
+load 275746-1.html
+load 276053-1.html
+load 280708-1.html
+load 280708-2.html
+load 281333-1.html
+load 285212-1.html
+load 286813-1.html
+load 306940-1.html
+load 310267-1.xml
+load 310638-1.svg
+load 310638-2.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/311661-1.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/311661-2.xhtml
+load 313086-1.xml
+load 317285-1.html
+load 317934-1.html
+load 320459-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/321058-1.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/321058-2.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/321077-1.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/321077-2.xhtml
+load 322436-1.html
+load 322678.html
+load 325024.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/325218.xhtml
+load 325967-1.html
+load 325984-1.xhtml
+load 325984-2.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/328944-1.xhtml
+load 329900-1.html
+load 330015-1.html
+load 331679-1.xhtml
+load 331679-2.xml
+load 331679-3.xml
+load 331883-1.html
+load 335140-1.html
+load 336291-1.html
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/336999-1.xhtml
+load 337066-1.xhtml
+load 337268-1.html
+load 337419-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/337476-1.xhtml
+load 338703-1.html
+load 339651-1.html
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/340093-1.xhtml
+load 341382-1.html
+load 341382-2.html
+load 341858-1.html
+load 342145-1.xhtml
+load 343293-1.xhtml
+load 343293-2.xhtml
+load 343540-1.html
+load 344057-1.xhtml
+load 344064-1.html
+load 344300-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/344340-1.xhtml
+load 347898-1.html
+load 348126-1.html
+load 348688-1.html
+load 348708-1.xhtml
+load 348729-1.html
+load 349095-1.xhtml
+load 350267-1.html
+load 354133-1.html
+load 354766-1.xhtml
+load 355989-1.xhtml
+load 355993-1.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/356325-1.xhtml
+load 358729-1.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/360339-1.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/360339-2.xhtml
+load 363729-1.html
+load 363729-2.html
+load 363729-3.html
+load 365909-1.xhtml
+load 365909-2.xhtml
+load 366128-1.xhtml
+load 366271-1.html
+load 366967-1.html
+load 367015-1.html
+load 367243-1.html
+load 369176-1.html
+load 369547-1.html
+load 369547-2.html
+load 369945-1.xhtml
+load 371681-1.xhtml
+load 372237-1.html
+load 372550-1.html
+load 373628-1.html
+load 374297-1.html
+load 374297-2.html
+load 378325-1.html
+load 378682.html
+load 379419-1.xhtml
+load 379799-1.html
+load 380096-1.html
+load 382204-1.html # bug 1323680
+load 383129-1.html
+load 384344-1.html
+load 384392-1.xhtml
+load 384392-2.svg
+load 384649-1.xhtml
+load 385354.html
+load 385866-1.xhtml
+load 385880-1.xhtml
+load 386266-1.html
+load 386476.html
+load 387195-1.html
+load 387195-2.xhtml
+load 388715-1.html
+load 390976-1.html
+load 393661-1.html
+load 393801-1.html
+load 394150-1.xhtml
+load 397011-1.xhtml
+load 398510-1.xhtml
+load 398733-1.html
+load 398733-2.html
+load 399132-1.xhtml
+load 399219-1.xhtml
+load 399365-1.html
+load 399676-1.xhtml
+load 399687-1.html
+load 399940-1.xhtml
+load 399951-1.html
+load 399994-1.html
+load 400445-1.xhtml
+load 400904-1.xhtml
+load 401734-1.html
+load 401734-2.html
+needs-focus pref(accessibility.browsewithcaret,true) load 403048.html
+load 403175-1.html
+load 403245-1.html
+load 403454.html
+load 403569-1.xhtml
+load 403569-2.xhtml
+load 403569-3.xhtml
+load 404491-1.html
+load 404721-1.xhtml
+load 404721-2.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/405049-1.xhtml
+load 406675-1.html
+load 408292.html
+load 408299.html
+load 408450-1.xhtml
+load 409461-1.xhtml
+load 410967.html
+load 411870-1.html
+load 412651-1.html
+load 413587-1.svg
+load 415503.xhtml
+asserts(1-1) load 416107.xhtml # bug 489100, ASSERTION: Out-of-flow frame got reflowed before its placeholder
+HTTP load 419985.html
+load 420031-1.html
+load 420213-1.html
+load 420219-1.html
+load 420651-1.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/421203-1.xhtml
+load 421432.html
+load 422276.html
+asserts(0-1) load 423107-1.xhtml # bug 866955
+load 425981-1.html
+load 428138-1.html
+load 428448-1.html
+load 429088-1.html
+load 429088-2.html
+load 429865-1.html
+load 429881.html
+load 430569-1.html
+load 430569-2.html
+load 432752-1.svg
+load 433450-1.html
+load 436982-1.html
+load 437142-1.html
+load 439258-1.html
+load 439343.html
+load 444863-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/444925-1.xhtml
+load 444967-1.html
+load 446328.html
+load 448488-1.html
+load 448543-1.html
+load 448543-2.html
+load 448543-3.html
+load 450319-1.xhtml
+asserts(5) asserts-if(Android,5-10) load 453894-1.xhtml # Bug 398043, plus crazy sizes during bidi reordering.
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/454751-1.xhtml
+load 455063-1.html
+load 455063-2.html
+load 455063-3.html
+load 455171-4.html
+load 455623-1.html
+skip-if(Android) load 457362-1.xhtml
+load 457514.html
+asserts(0-1) load 460389-1.html # bug 780985
+load 462392.html
+load 466763-1.html
+load 467881-1.html
+load 468491-1.html
+load 468555-1.xhtml
+load 468563-1.html
+load 468578-1.xhtml
+# These three didn't actually crash without the resizing that the
+# browser does when setting up print preview, but adding them anyway.
+load 468645-3.xhtml
+load 469861-1.xhtml
+load 469861-2.xhtml
+load 470851-1.xhtml
+asserts-if(Android&&!asyncPan,1-2) load 473042.xhtml # bug 1034369 (may also cause a few assertions to be registered on the next test)
+asserts(1) load 474075.html # bug 1775003
+load 477333-1.xhtml
+load 477731-1.html
+load 479114-1.html
+load 479360-1.xhtml
+load 480686-1.html
+load 481806-1.html
+load 483604-1.xhtml
+load 485501-1.html
+load 488390-1.xhtml
+load 489691.html
+load 490376-1.xhtml
+load 490559-1.html
+load 490747.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/491547-1.xhtml
+load chrome://reftest/content/crashtests/layout/base/crashtests/491547-2.xhtml
+load 492014.xhtml
+load 492112-1.xhtml
+load 492163-1.xhtml
+load 495350-1.html
+load 496011-1.xhtml
+load 499741-1.xhtml
+load 499841-1.xhtml
+load 499858-1.xhtml
+load 500467-1.html
+load 501878-1.html
+load 503936-1.html
+load 507119.html
+load 522374-1.html
+load 522374-2.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/526378-1.xhtml
+load 534367-1.xhtml
+load 534368-1.xhtml
+load 534768-1.html
+load 534768-2.html
+load 535721-1.xhtml
+load 535911-1.xhtml
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/536720.xhtml
+load 537562-1.xhtml
+load 537624-1.html
+load 537631-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/538082-1.xhtml
+load 538207-1.xhtml
+load 538210-1.html
+load 538267-1.html
+skip-if(Android) load chrome://reftest/content/crashtests/layout/base/crashtests/540760.xhtml
+load 540771-1.xhtml
+load 541869-1.xhtml
+load 541869-2.html
+load 543648-1.html
+load 560447-1.html
+load 564063-1.html
+load 569018-1.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/572003.xhtml
+load 572582-1.xhtml
+load 576649-1.html
+load 579655.html
+load 580129-1.html
+load 580494-1.html
+load 580834-1.xhtml
+load 589787.html
+load 591075-1.html
+load 591998-1.html
+load 595039-1.html
+load 597924-1.html
+load 606432-1.html
+load 609821-1.xhtml
+load 615146-1.html
+load 615781-1.xhtml
+load 616495-single-side-composite-color-border.html
+load 629035-1.html
+load 629908-1.html
+load 635329.html
+load 636229-1.html
+skip == 640272.html 640272-ref.html # Bug 1700265
+load 645193.html
+load 645572-1.html
+load 650475.xhtml
+load 650489.xhtml
+load 651342-1.html
+load 653133-1.html
+load 663295.html
+load 663662-1.html
+load 663662-2.html
+load 665837.html
+load 668579.html
+load 668941.xhtml
+load 670226.html
+asserts(2) load 675246-1.xhtml # Bug 675713
+asserts-if(Android,0-2) load 690247-1.html
+load 690619-1.html
+load 691118-1.html
+load 695861.html
+load 695964-1.svg
+load 698335.html
+needs-focus pref(accessibility.browsewithcaret,true) load 699353-1.html
+load 701504.html
+load 707098.html
+load 709536-1.xhtml
+load 722137.html
+load 725535.html
+load 727601.html
+asserts(0-2) pref(dom.disable_open_during_load,false) load 735943.html # the assertion is bug 735966
+asserts(0-2) load 736389-1.xhtml # sometimes the above assertions are delayed and is reported on this test instead
+load 736924-1.html
+load 749816-1.html
+asserts(1-1) load 763223-1.html # bogus size
+test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.lineThreshold,100) load 763702.xhtml
+load 767593-1.html
+load 767593-2.html
+load 770381-1.html
+load 772306.html
+load 788360.html
+load 793848.html
+load 795646.html
+skip load 802902.html # bug 901752
+load 806056-1.html
+load 806056-2.html
+load 812665.html
+load 813372-1.html
+load 817219.html
+load 818454.html
+load 822865.html
+load 824300.html
+load 824862.html
+load 826163.html
+load 827192.html
+load 833604-1.html
+load 835056.html
+load 836990-1.html
+load 840480.html
+load 842114.html
+load 847242.html
+# This test is slow, because it uses setTimeout a lot.
+# After bug 1875100, on windows, it times out because there's a left-over
+# window or so, and that bug causes us to throttle timers. Disable window
+# occlusion to work around this for now, see bug 1864255.
+pref(widget.windows.window_occlusion_tracking.enabled,false) skip-if(ThreadSanitizer) load 852293.html
+pref(layers.force-active,true) load 859526-1.html
+pref(layers.force-active,true) load 859630-1.html
+load 860579-1.html
+load 866588.html
+load 876092.html
+load 876221.html
+load 897852.html
+load 898913.html
+load 926728.html
+load 930381.html
+load 931450.html
+load 931460-1.html
+load 931464.html
+load 935765-1.html
+load 936988-1.html
+load 942690.html
+load 973390-1.html
+load 989994-1.html
+load 1001237.html
+load 1009036.html
+load 1043163-1.html
+load 1061028.html
+load 1107508-1.html
+load 1116104.html
+load 1127198-1.html
+load 1140198.html
+load 1143535.html
+load 1153716.html
+load 1156588.html
+load chrome://reftest/content/crashtests/layout/base/crashtests/1162813.xhtml
+load 1163583.html
+load 1234622-1.html
+load 1235467-1.html
+load 1261351.html
+load 1270797-1.html
+load 1278455-1.html
+load 1286889.html
+load 1288608.html
+load 1288946-1.html
+load 1288946-2a.html
+load 1288946-2b.html
+load 1297835.html
+load 1299736-1.html
+load 1308793.svg
+load 1308848-1.html
+load 1308848-2.html
+load 1338772-1.html
+load 1340571.html
+load 1343139-1.html
+asserts-if(Android,0-3) load 1343606.html # bug 1642521
+load 1343937.html
+load 1352380.html
+load 1362423-1.html
+load 1381323.html
+load 1382534.html
+load 1388625-1.html
+load 1390389.html
+load 1391736.html
+load 1395591-1.html
+load 1395715-1.html
+load 1397398-1.html
+load 1397398-2.html
+load 1397398-3.html
+load 1398500.html
+load 1400438-1.html
+load 1400599-1.html
+load 1401739.html
+load 1401840.html
+load 1402476.html
+load 1404789-2.html
+load 1406562.html
+load 1409147.html
+load 1411138.html
+load 1414100.html
+load 1414303.html
+load 1419762.html
+load 1419802.html
+load 1420533.html
+load 1422908.html
+load 1425893.html
+load 1425959.html
+load 1428353.html
+load 1428892.html
+load 1429088.html
+load 1429961.html
+load 1429962.html
+load 1435015.html
+load 1437155.html
+load 1439016.html
+load 1442018-1.html
+load 1442506.html
+load 1443027-1.html
+load 1448841-1.html
+load 1452839.html
+load 1453196.html
+load 1453342.html
+load 1453702.html
+load 1458121.html
+load 1461749.html
+load 1461812.html
+load 1462412.html
+load 1463940.html
+HTTP load 1464641.html
+load 1464737.html
+load 1466638.html
+pref(layout.css.motion-path-ray.enabled,true) asserts(1-1) load 1467519.html # bogus size
+load 1467688.html
+load 1467964.html
+load 1469354.html
+load 1470499.html
+pref(layout.accessiblecaret.enabled,true) load 1472020.html
+load 1472027.html
+asserts-if(Android,0-3) load 1477847.html
+pref(layout.accessiblecaret.enabled,true) load 1486521.html
+load 1489149.html
+load 1490037.html
+load 1494332.html
+load 1494030.html
+load 1505420.html
+load 1506163.html
+load 1506204.html
+load 1506314.html
+load 1507244.html
+load 1510080.html
+load 1510485.html
+pref(layout.css.individual-transform.enabled,true) load 1511442.html
+load 1511535.html
+load 1511563.html
+load 1516286-empty-mask.html
+load 1524382.html
+load 1524411.html
+load 1533885.html
+load 1534146.html
+load 1535945.html
+load 1539017.html
+load 1539303.html
+load 1541679.html
+load 1547261.html
+load 1547391.html
+pref(widget.windows.window_occlusion_tracking.enabled,false) load 1548057.html # Bug 1819154
+load 1549867.html
+load 1553874.html
+load 1560328.html
+load 1566672.html
+load 1574101-1.html
+load 1574101-2.html
+HTTP load 1575908-1.html
+load 1576972-1.html
+load 1578844-1.html
+load 1578844-2.html
+load 1579953-1.html
+load 1580576.html
+load 1586600.html
+load 1599518.html
+load 1599532.html
+pref(layout.accessiblecaret.enabled,true) load 1606492.html
+load 1654315.html
+load 1676301-1.html
+pref(apz.mvm.force-enabled,false) pref(dom.meta-viewport.enabled,false) pref(apz.allow_zooming,false) pref(layout.dynamic-toolbar-max-height,100) load 1689371.html
+load 1685146.html
+load 1689912.html
+load 1690163.html
+load 1723200.html
+load 1729578.html
+load 1729581.html
+load 1734007.html
+load 1745860.html
+pref(layout.accessiblecaret.enabled,true) load 1746989.html
+skip-if(Android) load 1747277-1.html
+load 1752649.html
+load 1753779.html
+pref(layout.css.backdrop-filter.enabled,true) load 1755790.html
+load 1771503.html
+load 1789934.html
+load 1791883.html
+load 1797995.html
+pref(layout.accessiblecaret.enabled,true) load 1818036.html
+pref(layout.accessiblecaret.enabled,true) load 1819239.html
+load 1821469.html
+load 1849898-1.html
diff --git a/layout/base/gtest/TestAccessibleCaretEventHub.cpp b/layout/base/gtest/TestAccessibleCaretEventHub.cpp
new file mode 100644
index 0000000000..6a3c9b61ac
--- /dev/null
+++ b/layout/base/gtest/TestAccessibleCaretEventHub.cpp
@@ -0,0 +1,785 @@
+/* -*- 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 "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <iostream>
+#include <string>
+
+#include "AccessibleCaretManager.h"
+
+#include "mozilla/AccessibleCaretEventHub.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TouchEvents.h"
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::DefaultValue;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+
+// -----------------------------------------------------------------------------
+// This file test the state transitions of AccessibleCaretEventHub under
+// various combination of events and callbacks.
+
+namespace mozilla {
+
+class MockAccessibleCaretManager : public AccessibleCaretManager {
+ public:
+ MockAccessibleCaretManager() : AccessibleCaretManager(nullptr) {}
+
+ MOCK_METHOD2(PressCaret,
+ nsresult(const nsPoint& aPoint, EventClassID aEventClass));
+ MOCK_METHOD1(DragCaret, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD0(ReleaseCaret, nsresult());
+ MOCK_METHOD1(TapCaret, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD1(SelectWordOrShortcut, nsresult(const nsPoint& aPoint));
+ MOCK_METHOD0(OnScrollStart, void());
+ MOCK_METHOD0(OnScrollEnd, void());
+ MOCK_METHOD0(OnScrollPositionChanged, void());
+ MOCK_METHOD0(OnBlur, void());
+};
+
+class MockAccessibleCaretEventHub : public AccessibleCaretEventHub {
+ public:
+ using AccessibleCaretEventHub::DragCaretState;
+ using AccessibleCaretEventHub::LongTapState;
+ using AccessibleCaretEventHub::NoActionState;
+ using AccessibleCaretEventHub::PressCaretState;
+ using AccessibleCaretEventHub::PressNoCaretState;
+ using AccessibleCaretEventHub::ScrollState;
+
+ MockAccessibleCaretEventHub() : AccessibleCaretEventHub(nullptr) {
+ mManager = MakeUnique<MockAccessibleCaretManager>();
+ mInitialized = true;
+ }
+
+ nsPoint GetTouchEventPosition(WidgetTouchEvent* aEvent,
+ int32_t aIdentifier) const override {
+ // Return the device point directly.
+ LayoutDeviceIntPoint touchIntPoint = aEvent->mTouches[0]->mRefPoint;
+ return nsPoint(touchIntPoint.x, touchIntPoint.y);
+ }
+
+ nsPoint GetMouseEventPosition(WidgetMouseEvent* aEvent) const override {
+ // Return the device point directly.
+ LayoutDeviceIntPoint mouseIntPoint = aEvent->AsGUIEvent()->mRefPoint;
+ return nsPoint(mouseIntPoint.x, mouseIntPoint.y);
+ }
+
+ MockAccessibleCaretManager* GetMockAccessibleCaretManager() {
+ return static_cast<MockAccessibleCaretManager*>(mManager.get());
+ }
+};
+
+// Print the name of the state for debugging.
+static ::std::ostream& operator<<(
+ ::std::ostream& aOstream,
+ const MockAccessibleCaretEventHub::State* aState) {
+ return aOstream << aState->Name();
+}
+
+class AccessibleCaretEventHubTester : public ::testing::Test {
+ public:
+ AccessibleCaretEventHubTester() {
+ DefaultValue<nsresult>::Set(NS_OK);
+ EXPECT_EQ(mHub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ // AccessibleCaretEventHub requires the caller to hold a ref to it. We just
+ // add ref here for the sake of convenience.
+ mHub.get()->AddRef();
+ }
+
+ ~AccessibleCaretEventHubTester() override {
+ // Release the ref added in the constructor.
+ mHub.get()->Release();
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseEvent(EventMessage aMessage,
+ nscoord aX, nscoord aY) {
+ auto event = MakeUnique<WidgetMouseEvent>(true, aMessage, nullptr,
+ WidgetMouseEvent::eReal);
+
+ event->mButton = MouseButton::ePrimary;
+ event->mRefPoint = LayoutDeviceIntPoint(aX, aY);
+
+ return std::move(event);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMousePressEvent(nscoord aX, nscoord aY) {
+ return CreateMouseEvent(eMouseDown, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseMoveEvent(nscoord aX, nscoord aY) {
+ return CreateMouseEvent(eMouseMove, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateMouseReleaseEvent(nscoord aX,
+ nscoord aY) {
+ return CreateMouseEvent(eMouseUp, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateLongTapEvent(nscoord aX, nscoord aY) {
+ return CreateMouseEvent(eMouseLongTap, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchEvent(EventMessage aMessage,
+ nscoord aX, nscoord aY) {
+ auto event = MakeUnique<WidgetTouchEvent>(true, aMessage, nullptr);
+ int32_t identifier = 0;
+ LayoutDeviceIntPoint point(aX, aY);
+ LayoutDeviceIntPoint radius(19, 19);
+ float rotationAngle = 0;
+ float force = 1;
+
+ RefPtr<dom::Touch> touch(
+ new dom::Touch(identifier, point, radius, rotationAngle, force));
+ event->mTouches.AppendElement(touch);
+
+ return std::move(event);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchStartEvent(nscoord aX, nscoord aY) {
+ return CreateTouchEvent(eTouchStart, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchMoveEvent(nscoord aX, nscoord aY) {
+ return CreateTouchEvent(eTouchMove, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchEndEvent(nscoord aX, nscoord aY) {
+ return CreateTouchEvent(eTouchEnd, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateTouchCancelEvent(nscoord aX, nscoord aY) {
+ return CreateTouchEvent(eTouchCancel, aX, aY);
+ }
+
+ static UniquePtr<WidgetEvent> CreateWheelEvent(EventMessage aMessage) {
+ auto event = MakeUnique<WidgetWheelEvent>(true, aMessage, nullptr);
+
+ return std::move(event);
+ }
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestAsyncPanZoomScroll();
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void HandleEventAndCheckState(
+ UniquePtr<WidgetEvent> aEvent,
+ MockAccessibleCaretEventHub::State* aExpectedState,
+ nsEventStatus aExpectedEventStatus) {
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ nsEventStatus rv = hub->HandleEvent(aEvent.get());
+ EXPECT_EQ(hub->GetState(), aExpectedState);
+ EXPECT_EQ(rv, aExpectedEventStatus);
+ }
+
+ void CheckState(MockAccessibleCaretEventHub::State* aExpectedState) {
+ EXPECT_EQ(mHub->GetState(), aExpectedState);
+ }
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressReleaseOnCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressMoveReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestPressMoveReleaseOnCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestLongTapWithSelectWordSuccessful(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestLongTapWithSelectWordFailed(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void TestEventDrivenAsyncPanZoomScroll(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator);
+
+ // Member variables
+ RefPtr<MockAccessibleCaretEventHub> mHub{new MockAccessibleCaretEventHub()};
+
+}; // class AccessibleCaretEventHubTester
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnNoCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressReleaseOnNoCaret(CreateMousePressEvent, CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnNoCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestPressReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret()).Times(0);
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_)).Times(0);
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressReleaseOnCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressReleaseOnCaret(CreateMousePressEvent, CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressReleaseOnCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressReleaseOnCaret(CreateTouchStartEvent, CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestPressReleaseOnCaret(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .Times(0);
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), TapCaret(_));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnNoCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressMoveReleaseOnNoCaret(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnNoCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressMoveReleaseOnNoCaret(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestPressMoveReleaseOnNoCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(x0, y0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x1, y1),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A large move to simulate a dragging to select text since the distance
+ // between (x0, y0) and (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x2, y2),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMousePressMoveReleaseOnCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressMoveReleaseOnCaret(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchPressMoveReleaseOnCaret)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestPressMoveReleaseOnCaret(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestPressMoveReleaseOnCaret(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_))
+ .Times(2) // two valid drag operations
+ .WillRepeatedly(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret())
+ .WillOnce(Return(NS_OK));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // A large move forms a valid drag since the distance between (x0, y0) and
+ // (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ // Also a valid drag since the distance between (x0, y0) and (x3, y3) above
+ // the tolerance value even if the distance between (x2, y2) and (x3, y3) is
+ // below the tolerance value.
+ HandleEventAndCheckState(aMoveEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(aReleaseEventCreator(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+}
+
+TEST_F(AccessibleCaretEventHubTester,
+ TestTouchStartMoveEndOnCaretWithTouchCancelIgnored)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ nscoord x0 = 0, y0 = 0;
+ nscoord x1 = 100, y1 = 100;
+ nscoord x2 = 300, y2 = 300;
+ nscoord x3 = 400, y3 = 400;
+
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), ReleaseCaret())
+ .WillOnce(Return(NS_OK));
+ }
+
+ // All the eTouchCancel events should be ignored in this test.
+
+ HandleEventAndCheckState(CreateTouchStartEvent(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x0, y0),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A small move with the distance between (x0, y0) and (x1, y1) below the
+ // tolerance value.
+ HandleEventAndCheckState(CreateTouchMoveEvent(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x1, y1),
+ MockAccessibleCaretEventHub::PressCaretState(),
+ nsEventStatus_eIgnore);
+
+ // A large move forms a valid drag since the distance between (x0, y0) and
+ // (x2, y2) is above the tolerance value.
+ HandleEventAndCheckState(CreateTouchMoveEvent(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x2, y2),
+ MockAccessibleCaretEventHub::DragCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateTouchEndEvent(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eConsumeNoDefault);
+
+ HandleEventAndCheckState(CreateTouchCancelEvent(x3, y3),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordSuccessful)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestLongTapWithSelectWordSuccessful(CreateMousePressEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordSuccessful)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestLongTapWithSelectWordSuccessful(CreateTouchStartEvent,
+ CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestLongTapWithSelectWordSuccessful(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(check, Call("longtap with scrolling"));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_OK));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ // Test long tap without scrolling.
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+
+ // On Fennec, after long tap, the script might scroll and zoom the input field
+ // to the center of the screen to make typing easier before the user lifts the
+ // finger.
+ check.Call("longtap with scrolling");
+
+ HandleEventAndCheckState(aPressEventCreator(1, 1),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(1, 1),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(1, 1),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseLongTapWithSelectWordFailed)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestLongTapWithSelectWordFailed(CreateMousePressEvent,
+ CreateMouseReleaseEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchLongTapWithSelectWordFailed)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestLongTapWithSelectWordFailed(CreateTouchStartEvent, CreateTouchEndEvent);
+}
+
+template <typename PressEventCreator, typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestLongTapWithSelectWordFailed(
+ PressEventCreator aPressEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), SelectWordOrShortcut(_))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ }
+
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(CreateLongTapEvent(0, 0),
+ MockAccessibleCaretEventHub::LongTapState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aReleaseEventCreator(0, 0),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestTouchEventDrivenAsyncPanZoomScroll)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestEventDrivenAsyncPanZoomScroll(CreateTouchStartEvent, CreateTouchMoveEvent,
+ CreateTouchEndEvent);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestMouseEventDrivenAsyncPanZoomScroll)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ TestEventDrivenAsyncPanZoomScroll(CreateMousePressEvent, CreateMouseMoveEvent,
+ CreateMouseReleaseEvent);
+}
+
+template <typename PressEventCreator, typename MoveEventCreator,
+ typename ReleaseEventCreator>
+void AccessibleCaretEventHubTester::TestEventDrivenAsyncPanZoomScroll(
+ PressEventCreator aPressEventCreator, MoveEventCreator aMoveEventCreator,
+ ReleaseEventCreator aReleaseEventCreator) {
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+
+ EXPECT_CALL(check, Call("1"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+
+ EXPECT_CALL(check, Call("2"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), PressCaret(_, _))
+ .WillOnce(Return(NS_ERROR_FAILURE));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), DragCaret(_)).Times(0);
+
+ EXPECT_CALL(check, Call("3"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ // Receive press event.
+ HandleEventAndCheckState(aPressEventCreator(0, 0),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aMoveEventCreator(100, 100),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("1");
+
+ // Event driven scroll started
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ HandleEventAndCheckState(aMoveEventCreator(160, 160),
+ MockAccessibleCaretEventHub::ScrollState(),
+ nsEventStatus_eIgnore);
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ // Event driven scroll ended
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(210, 210),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("2");
+
+ // Receive another press event.
+ HandleEventAndCheckState(aPressEventCreator(220, 220),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ HandleEventAndCheckState(aMoveEventCreator(230, 230),
+ MockAccessibleCaretEventHub::PressNoCaretState(),
+ nsEventStatus_eIgnore);
+
+ check.Call("3");
+
+ // Another APZ scroll started
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ // Another APZ scroll ended
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ HandleEventAndCheckState(aReleaseEventCreator(310, 310),
+ MockAccessibleCaretEventHub::NoActionState(),
+ nsEventStatus_eIgnore);
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScroll)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION { TestAsyncPanZoomScroll(); }
+
+void AccessibleCaretEventHubTester::TestAsyncPanZoomScroll() {
+ MockFunction<void(::std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(check, Call("1"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(),
+ OnScrollPositionChanged());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+
+ EXPECT_CALL(check, Call("2"));
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(),
+ OnScrollPositionChanged());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ }
+
+ // First APZ scrolling.
+ check.Call("1");
+
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ // Second APZ scrolling.
+ check.Call("2");
+
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollStartedThenBlur)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd()).Times(0);
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur());
+ }
+
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->NotifyBlur(true);
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+TEST_F(AccessibleCaretEventHubTester, TestAsyncPanZoomScrollEndedThenBlur)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollStart());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnScrollEnd());
+ EXPECT_CALL(*mHub->GetMockAccessibleCaretManager(), OnBlur());
+ }
+
+ RefPtr<MockAccessibleCaretEventHub> hub(mHub);
+ hub->AsyncPanZoomStarted();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->ScrollPositionChanged();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::ScrollState());
+
+ hub->AsyncPanZoomStopped();
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+
+ hub->NotifyBlur(true);
+ EXPECT_EQ(hub->GetState(), MockAccessibleCaretEventHub::NoActionState());
+}
+
+} // namespace mozilla
diff --git a/layout/base/gtest/TestAccessibleCaretManager.cpp b/layout/base/gtest/TestAccessibleCaretManager.cpp
new file mode 100644
index 0000000000..464ad68fb0
--- /dev/null
+++ b/layout/base/gtest/TestAccessibleCaretManager.cpp
@@ -0,0 +1,848 @@
+/* -*- 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 "gtest/gtest.h"
+#include "gmock/gmock.h"
+
+#include <string>
+
+#include "AccessibleCaret.h"
+#include "AccessibleCaretManager.h"
+#include "mozilla/Preferences.h"
+
+using ::testing::_;
+using ::testing::DefaultValue;
+using ::testing::Eq;
+using ::testing::InSequence;
+using ::testing::MockFunction;
+using ::testing::Return;
+
+// -----------------------------------------------------------------------------
+// This file tests CaretStateChanged events and the appearance of the two
+// AccessibleCarets manipulated by AccessibleCaretManager.
+
+namespace mozilla {
+using dom::CaretChangedReason;
+
+class MOZ_RAII AutoRestoreBoolPref final {
+ public:
+ AutoRestoreBoolPref(const char* aPref, bool aValue) : mPref(aPref) {
+ Preferences::GetBool(mPref, &mOldValue);
+ Preferences::SetBool(mPref, aValue);
+ }
+
+ ~AutoRestoreBoolPref() { Preferences::SetBool(mPref, mOldValue); }
+
+ private:
+ const char* mPref = nullptr;
+ bool mOldValue = false;
+};
+
+class AccessibleCaretManagerTester : public ::testing::Test {
+ public:
+ class MockAccessibleCaret : public AccessibleCaret {
+ public:
+ MockAccessibleCaret() : AccessibleCaret(nullptr) {}
+
+ void SetAppearance(Appearance aAppearance) override {
+ // A simplified version without touching CaretElement().
+ mAppearance = aAppearance;
+ }
+
+ MOCK_METHOD2(SetPosition,
+ PositionChangedResult(nsIFrame* aFrame, int32_t aOffset));
+
+ }; // class MockAccessibleCaret
+
+ class MockAccessibleCaretManager : public AccessibleCaretManager {
+ public:
+ using CaretMode = AccessibleCaretManager::CaretMode;
+ using AccessibleCaretManager::HideCaretsAndDispatchCaretStateChangedEvent;
+ using AccessibleCaretManager::UpdateCarets;
+
+ MockAccessibleCaretManager()
+ : AccessibleCaretManager(nullptr,
+ Carets{MakeUnique<MockAccessibleCaret>(),
+ MakeUnique<MockAccessibleCaret>()}) {}
+
+ MockAccessibleCaret& FirstCaret() {
+ return static_cast<MockAccessibleCaret&>(*mCarets.GetFirst());
+ }
+
+ MockAccessibleCaret& SecondCaret() {
+ return static_cast<MockAccessibleCaret&>(*mCarets.GetSecond());
+ }
+
+ bool CompareTreePosition(nsIFrame* aStartFrame,
+ nsIFrame* aEndFrame) const override {
+ return true;
+ }
+
+ bool IsCaretDisplayableInCursorMode(
+ nsIFrame** aOutFrame = nullptr,
+ int32_t* aOutOffset = nullptr) const override {
+ return true;
+ }
+
+ bool UpdateCaretsForOverlappingTilt() override { return true; }
+
+ void UpdateCaretsForAlwaysTilt(const nsIFrame* aStartFrame,
+ const nsIFrame* aEndFrame) override {
+ if (mCarets.GetFirst()->IsVisuallyVisible()) {
+ mCarets.GetFirst()->SetAppearance(Appearance::Left);
+ }
+ if (mCarets.GetSecond()->IsVisuallyVisible()) {
+ mCarets.GetSecond()->SetAppearance(Appearance::Right);
+ }
+ }
+
+ Terminated IsTerminated() const override { return Terminated::No; }
+ bool IsScrollStarted() const { return mIsScrollStarted; }
+
+ Terminated MaybeFlushLayout() override { return Terminated::No; }
+
+ MOCK_CONST_METHOD0(GetCaretMode, CaretMode());
+ MOCK_METHOD2(DispatchCaretStateChangedEvent,
+ void(CaretChangedReason aReason, const nsPoint* aPoint));
+ MOCK_CONST_METHOD1(HasNonEmptyTextContent, bool(nsINode* aNode));
+
+ }; // class MockAccessibleCaretManager
+
+ using Appearance = AccessibleCaret::Appearance;
+ using PositionChangedResult = AccessibleCaret::PositionChangedResult;
+ using CaretMode = MockAccessibleCaretManager::CaretMode;
+
+ AccessibleCaretManagerTester() {
+ DefaultValue<CaretMode>::Set(CaretMode::None);
+ DefaultValue<PositionChangedResult>::Set(PositionChangedResult::NotChanged);
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Position));
+
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Position));
+ }
+
+ AccessibleCaret::Appearance FirstCaretAppearance() {
+ return mManager.FirstCaret().GetAppearance();
+ }
+
+ AccessibleCaret::Appearance SecondCaretAppearance() {
+ return mManager.SecondCaret().GetAppearance();
+ }
+
+ // Member variables
+ MockAccessibleCaretManager mManager;
+
+}; // class AccessibleCaretManagerTester
+
+TEST_F(AccessibleCaretManagerTester, TestUpdatesInSelectionMode)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Set default preference.
+ AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", false);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(3);
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnNonEmptyInput)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("mouse down"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("reflow"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("blur"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("mouse up"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("reflow2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ }
+
+ // Simulate a single tap on a non-empty input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("update");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("mouse down");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("reflow");
+
+ mManager.OnBlur();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("blur");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::MOUSEUP_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("mouse up");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("reflow2");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestSingleTapOnEmptyInput)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Set default preference.
+ AutoRestoreBoolPref savedPref(
+ "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content",
+ false);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("mouse down"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("reflow"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("blur"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("mouse up"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("reflow2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ }
+
+ // Simulate a single tap on an empty input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("update");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::DRAG_REASON |
+ nsISelectionListener::MOUSEDOWN_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("mouse down");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("reflow");
+
+ mManager.OnBlur();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("blur");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::MOUSEUP_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("mouse up");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("reflow2");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestTypingAtEndOfInput)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("update"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("keyboard"));
+
+ // No CaretStateChanged events should be dispatched since the caret has
+ // being hidden in cursor mode.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ }
+
+ // Simulate typing the end of the input.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("update");
+
+ mManager.OnKeyboardEvent();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("keyboard");
+
+ mManager.OnSelectionChanged(nullptr, nullptr,
+ nsISelectionListener::NO_REASON);
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInSelectionMode)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Set default preference.
+ AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", false);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ // Initially, first caret is out of scrollport, and second caret is visible.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("reflow1"));
+
+ // After scroll ended, first caret is visible and second caret is out of
+ // scroll port.
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("reflow2"));
+
+ // After the scroll ended, both carets are visible.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("scrollstart1");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("reflow1");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollstart2");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("reflow2");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Normal);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester,
+ TestScrollInSelectionModeWithAlwaysTiltPref)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Simulate Firefox Android preference.
+ AutoRestoreBoolPref savedPref("layout.accessiblecaret.always_tilt", true);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Selection));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ // Initially, first caret is out of scrollport, and second caret is visible.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("scrollPositionChanged1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("reflow1"));
+
+ // After scroll ended, first caret is visible and second caret is out of
+ // scroll port.
+ EXPECT_CALL(mManager.SecondCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(_, nullptr)).Times(0);
+ EXPECT_CALL(check, Call("scrollPositionChanged2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("reflow2"));
+
+ // After the scroll ended, both carets are visible.
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("scrollstart1");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("scrollPositionChanged1");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("reflow1");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollstart2");
+
+ mManager.OnScrollPositionChanged();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollPositionChanged2");
+
+ mManager.OnReflow();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("reflow2");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Left);
+ EXPECT_EQ(SecondCaretAppearance(), Appearance::Right);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenLogicallyVisible)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ // After scroll ended, the caret is out of scroll port.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ // After scroll ended, the caret is visible again.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Position));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("scrollstart1");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollstart2");
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeWhenHidden)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_)).WillRepeatedly(Return(true));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Visibilitychange, nullptr))
+ .Times(1);
+ EXPECT_CALL(check, Call("hidecarets"));
+
+ // After scroll ended, the caret is out of scroll port.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ // After scroll ended, the caret is visible again.
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillRepeatedly(Return(PositionChangedResult::Position));
+ EXPECT_CALL(check, Call("scrollend2"));
+ }
+
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("updatecarets");
+
+ mManager.HideCaretsAndDispatchCaretStateChangedEvent();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("hidecarets");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollend1");
+
+ mManager.OnScrollStart();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("scrollend2");
+}
+
+TEST_F(AccessibleCaretManagerTester, TestScrollInCursorModeOnEmptyContent)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Set default preference.
+ AutoRestoreBoolPref savedPref(
+ "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content",
+ false);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart1"));
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("scrollstart3"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("scrollend3"));
+ }
+
+ // Simulate a pinch-zoom operation before tapping on an empty content.
+ mManager.OnScrollStart();
+ mManager.OnScrollEnd();
+ EXPECT_EQ(mManager.IsScrollStarted(), false);
+
+ // Simulate a single tap on an empty content.
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("updatecarets");
+
+ // Scroll the caret to be out of the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart1");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend1");
+
+ // Scroll the caret into the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart2");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend2");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ check.Call("scrollstart3");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("scrollend3");
+}
+
+TEST_F(AccessibleCaretManagerTester,
+ TestScrollInCursorModeWithCaretShownWhenLongTappingOnEmptyContentPref)
+MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
+ // Simulate Firefox Android preference.
+ AutoRestoreBoolPref savedPref(
+ "layout.accessiblecaret.caret_shown_when_long_tapping_on_empty_content",
+ true);
+
+ EXPECT_CALL(mManager, GetCaretMode())
+ .WillRepeatedly(Return(CaretMode::Cursor));
+
+ EXPECT_CALL(mManager, HasNonEmptyTextContent(_))
+ .WillRepeatedly(Return(false));
+
+ MockFunction<void(std::string aCheckPointName)> check;
+ {
+ InSequence dummy;
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("singletap updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("longtap updatecarets"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollstart1"));
+
+ EXPECT_CALL(mManager.FirstCaret(), SetPosition(_, _))
+ .WillOnce(Return(PositionChangedResult::Invisible));
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollend1"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollstart2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollend2"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Scroll, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollstart3"));
+
+ EXPECT_CALL(mManager, DispatchCaretStateChangedEvent(
+ CaretChangedReason::Updateposition, nullptr));
+ EXPECT_CALL(check, Call("longtap scrollend3"));
+ }
+
+ // Simulate a single tap on an empty input.
+ mManager.FirstCaret().SetAppearance(Appearance::None);
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+ check.Call("singletap updatecarets");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::None);
+
+ // Simulate a long tap on an empty input.
+ mManager.FirstCaret().SetAppearance(Appearance::Normal);
+ mManager.UpdateCarets();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap updatecarets");
+
+ // Scroll the caret to be out of the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart1");
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::NormalNotShown);
+ check.Call("longtap scrollend1");
+
+ // Scroll the caret into the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart2");
+ mManager.OnScrollPositionChanged();
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap scrollend2");
+
+ // Scroll the caret within the viewport.
+ mManager.OnScrollStart();
+ check.Call("longtap scrollstart3");
+ mManager.OnScrollPositionChanged();
+ mManager.OnScrollEnd();
+ EXPECT_EQ(FirstCaretAppearance(), Appearance::Normal);
+ check.Call("longtap scrollend3");
+}
+
+} // namespace mozilla
diff --git a/layout/base/gtest/moz.build b/layout/base/gtest/moz.build
new file mode 100644
index 0000000000..b082855295
--- /dev/null
+++ b/layout/base/gtest/moz.build
@@ -0,0 +1,26 @@
+# -*- 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/.
+
+
+UNIFIED_SOURCES += [
+ "TestAccessibleCaretEventHub.cpp",
+ "TestAccessibleCaretManager.cpp",
+]
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG["CC_TYPE"] == "clang":
+ CXXFLAGS += ["-Wno-inconsistent-missing-override"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "/docshell/base",
+ "/layout/base",
+ "/layout/style",
+]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/layout/base/metrics.yaml b/layout/base/metrics.yaml
new file mode 100644
index 0000000000..856ff77965
--- /dev/null
+++ b/layout/base/metrics.yaml
@@ -0,0 +1,50 @@
+# 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/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - 'Core :: Layout'
+
+performance.pageload:
+ req_anim_frame_callback:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: PERF_REQUEST_ANIMATION_CALLBACK_PAGELOAD_MS
+ description: >
+ Time spent in milliseconds calling all request animation frame callbacks
+ for a document before it has reached readystate complete.
+ (Migrated from the geckoview metric of the same name.)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1671729
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877842
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1671729#c7
+ notification_emails:
+ - dpalmeiro@mozilla.com
+ - perf-telemetry-alerts@mozilla.com
+ expires: never
+
+
+performance.responsiveness:
+ req_anim_frame_callback:
+ type: timing_distribution
+ time_unit: millisecond
+ telemetry_mirror: PERF_REQUEST_ANIMATION_CALLBACK_NON_PAGELOAD_MS
+ description: >
+ Time spent in milliseconds calling all request animation frame callbacks
+ for a document after it has reached readystate complete.
+ (Migrated from the geckoview metric of the same name.)
+ bugs:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1671729
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1877842
+ data_reviews:
+ - https://bugzilla.mozilla.org/show_bug.cgi?id=1671729#c7
+ notification_emails:
+ - dpalmeiro@mozilla.com
+ - perf-telemetry-alerts@mozilla.com
+ expires: never
diff --git a/layout/base/moz.build b/layout/base/moz.build
new file mode 100644
index 0000000000..68d70bad31
--- /dev/null
+++ b/layout/base/moz.build
@@ -0,0 +1,188 @@
+# -*- 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("Restyle*"):
+ BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("nsStyle*"):
+ BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("nsChangeHint.h"):
+ BUG_COMPONENT = ("Core", "CSS Parsing and Computation")
+
+with Files("nsBidi*"):
+ BUG_COMPONENT = ("Core", "Layout: Text and Fonts")
+
+with Files("AccessibleCaret*"):
+ BUG_COMPONENT = ("Core", "DOM: Selection")
+
+XPIDL_SOURCES += [
+ "nsILayoutHistoryState.idl",
+ "nsIPreloadedStyleSheet.idl",
+ "nsIStyleSheetService.idl",
+]
+
+if CONFIG["MOZ_DEBUG"]:
+ UNIFIED_SOURCES += [
+ "nsAutoLayoutPhase.cpp",
+ ]
+
+XPIDL_MODULE = "layout_base"
+
+EXPORTS += [
+ "FrameProperties.h",
+ "LayoutConstants.h",
+ "LayoutLogging.h",
+ "MobileViewportManager.h",
+ "nsAutoLayoutPhase.h",
+ "nsBidiPresUtils.h",
+ "nsCaret.h",
+ "nsChangeHint.h",
+ "nsCompatibility.h",
+ "nsCounterManager.h",
+ "nsCSSFrameConstructor.h",
+ "nsFrameManager.h",
+ "nsFrameTraversal.h",
+ "nsGenConList.h",
+ "nsIPercentBSizeObserver.h",
+ "nsIReflowCallback.h",
+ "nsLayoutUtils.h",
+ "nsPresArena.h",
+ "nsPresArenaObjectList.h",
+ "nsPresContext.h",
+ "nsPresContextInlines.h",
+ "nsQuoteList.h",
+ "nsRefreshDriver.h",
+ "nsRefreshObservers.h",
+ "nsStyleChangeList.h",
+ "nsStyleSheetService.h",
+ "StackArena.h",
+ "TouchManager.h",
+ "Units.h",
+ "UnitTransforms.h",
+ "WordMovementType.h",
+ "ZoomConstraintsClient.h",
+]
+
+EXPORTS.mozilla += [
+ "AccessibleCaretEventHub.h",
+ "ArenaObjectID.h",
+ "Baseline.h",
+ "CaretAssociationHint.h",
+ "ContainStyleScopeManager.h",
+ "DepthOrderedFrameList.h",
+ "DisplayPortUtils.h",
+ "GeckoMVMContext.h",
+ "GeometryUtils.h",
+ "MediaEmulationData.h",
+ "MotionPathUtils.h",
+ "MVMContext.h",
+ "OverflowChangedTracker.h",
+ "PositionedEventTargeting.h",
+ "PresShell.h",
+ "PresShellForwards.h",
+ "PresShellInlines.h",
+ "RelativeTo.h",
+ "RestyleManager.h",
+ "ScrollStyles.h",
+ "ScrollTypes.h",
+ "ShapeUtils.h",
+ "StaticPresData.h",
+ "SurfaceFromElementResult.h",
+ "ViewportUtils.h",
+]
+
+EXPORTS.mozilla.layout += [
+ "LayoutTelemetryTools.h",
+]
+
+UNIFIED_SOURCES += [
+ "AccessibleCaret.cpp",
+ "AccessibleCaretEventHub.cpp",
+ "AccessibleCaretManager.cpp",
+ "Baseline.cpp",
+ "ContainStyleScopeManager.cpp",
+ "DepthOrderedFrameList.cpp",
+ "DisplayPortUtils.cpp",
+ "GeckoMVMContext.cpp",
+ "GeometryUtils.cpp",
+ "LayoutLogging.cpp",
+ "LayoutTelemetryTools.cpp",
+ "MobileViewportManager.cpp",
+ "MotionPathUtils.cpp",
+ "nsBidiPresUtils.cpp",
+ "nsCaret.cpp",
+ "nsCounterManager.cpp",
+ "nsCSSColorUtils.cpp",
+ "nsCSSFrameConstructor.cpp",
+ "nsDocumentViewer.cpp",
+ "nsFrameManager.cpp",
+ "nsFrameTraversal.cpp",
+ "nsGenConList.cpp",
+ "nsLayoutDebugger.cpp",
+ "nsLayoutHistoryState.cpp",
+ "nsLayoutUtils.cpp",
+ "nsPresArena.cpp",
+ "nsPresContext.cpp",
+ "nsQuoteList.cpp",
+ "nsRefreshObservers.cpp",
+ "nsStyleChangeList.cpp",
+ "nsStyleSheetService.cpp",
+ "PositionedEventTargeting.cpp",
+ "PresShell.cpp",
+ "RestyleManager.cpp",
+ "ScrollStyles.cpp",
+ "ShapeUtils.cpp",
+ "StackArena.cpp",
+ "StaticPresData.cpp",
+ "TouchManager.cpp",
+ "ViewportUtils.cpp",
+ "ZoomConstraintsClient.cpp",
+]
+
+# nsRefreshDriver.cpp needs to be built separately because of name clashes in the OS X headers
+SOURCES += [
+ "nsRefreshDriver.cpp",
+]
+
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += ["gtest"]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+LOCAL_INCLUDES += [
+ "../forms",
+ "../generic",
+ "../mathml",
+ "../painting",
+ "../printing",
+ "../style",
+ "../tables",
+ "../xul",
+ "../xul/tree/",
+ "/docshell/base",
+ "/dom/base",
+ "/dom/html",
+ "/dom/svg",
+ "/dom/xul",
+ "/view",
+]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android":
+ LOCAL_INCLUDES += [
+ "/widget/android",
+ ]
+
+IPDL_SOURCES += [
+ "PresState.ipdlh",
+]
+
+FINAL_LIBRARY = "xul"
+
+BROWSER_CHROME_MANIFESTS += ["tests/browser.toml"]
+MARIONETTE_MANIFESTS += ["tests/marionette/manifest.toml"]
+MOCHITEST_MANIFESTS += ["tests/mochitest.toml"]
+MOCHITEST_CHROME_MANIFESTS += ["tests/chrome/chrome.toml"]
diff --git a/layout/base/nsAutoLayoutPhase.cpp b/layout/base/nsAutoLayoutPhase.cpp
new file mode 100644
index 0000000000..94258f943e
--- /dev/null
+++ b/layout/base/nsAutoLayoutPhase.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/. */
+
+#ifndef DEBUG
+static_assert(false, "This should not be compiled in !DEBUG");
+#endif // DEBUG
+
+#include "nsAutoLayoutPhase.h"
+#include "nsPresContext.h"
+#include "nsContentUtils.h"
+
+nsAutoLayoutPhase::nsAutoLayoutPhase(nsPresContext* aPresContext,
+ nsLayoutPhase aPhase)
+ : mPresContext(aPresContext), mPhase(aPhase), mCount(0) {
+ Enter();
+}
+
+nsAutoLayoutPhase::~nsAutoLayoutPhase() {
+ Exit();
+ MOZ_ASSERT(mCount == 0, "imbalanced");
+}
+
+void nsAutoLayoutPhase::Enter() {
+ switch (mPhase) {
+ case nsLayoutPhase::Paint:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Paint] == 0,
+ "recurring into paint");
+ MOZ_ASSERT(
+ mPresContext->mLayoutPhaseCount[nsLayoutPhase::DisplayListBuilding] ==
+ 0,
+ "recurring into paint from display list building");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Reflow] == 0,
+ "painting in the middle of reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::FrameC] == 0,
+ "painting in the middle of frame construction");
+ break;
+ case nsLayoutPhase::DisplayListBuilding:
+ // It's fine and expected to be in a paint here.
+ MOZ_ASSERT(
+ mPresContext->mLayoutPhaseCount[nsLayoutPhase::DisplayListBuilding] ==
+ 0,
+ "recurring into display list building");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Reflow] == 0,
+ "display list building in the middle of reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::FrameC] == 0,
+ "display list building in the middle of frame construction");
+ break;
+ case nsLayoutPhase::Reflow:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Paint] == 0,
+ "reflowing in the middle of a paint");
+ MOZ_ASSERT(
+ mPresContext->mLayoutPhaseCount[nsLayoutPhase::DisplayListBuilding] ==
+ 0,
+ "reflowing in the middle of a display list building");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Reflow] == 0,
+ "recurring into reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::FrameC] == 0,
+ "reflowing in the middle of frame construction");
+ break;
+ case nsLayoutPhase::FrameC:
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Paint] == 0,
+ "constructing frames in the middle of a paint");
+ MOZ_ASSERT(
+ mPresContext->mLayoutPhaseCount[nsLayoutPhase::DisplayListBuilding] ==
+ 0,
+ "constructing frames in the middle of a display list building");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::Reflow] == 0,
+ "constructing frames in the middle of reflow");
+ MOZ_ASSERT(mPresContext->mLayoutPhaseCount[nsLayoutPhase::FrameC] == 0,
+ "recurring into frame construction");
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "constructing frames and scripts are not blocked");
+ break;
+ case nsLayoutPhase::COUNT:
+ break;
+ }
+
+ ++(mPresContext->mLayoutPhaseCount[mPhase]);
+ ++mCount;
+}
+
+void nsAutoLayoutPhase::Exit() {
+ MOZ_ASSERT(mCount > 0 && mPresContext->mLayoutPhaseCount[mPhase] > 0,
+ "imbalanced");
+ --(mPresContext->mLayoutPhaseCount[mPhase]);
+ --mCount;
+}
diff --git a/layout/base/nsAutoLayoutPhase.h b/layout/base/nsAutoLayoutPhase.h
new file mode 100644
index 0000000000..cba7a644f9
--- /dev/null
+++ b/layout/base/nsAutoLayoutPhase.h
@@ -0,0 +1,49 @@
+/* -*- 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 nsAutoLayoutPhase_h
+#define nsAutoLayoutPhase_h
+
+#ifdef DEBUG
+
+# include <stdint.h>
+
+enum class nsLayoutPhase : uint8_t;
+class nsPresContext;
+
+struct nsAutoLayoutPhase {
+ nsAutoLayoutPhase(nsPresContext* aPresContext, nsLayoutPhase aPhase);
+ ~nsAutoLayoutPhase();
+
+ void Enter();
+ void Exit();
+
+ private:
+ nsPresContext* mPresContext;
+ nsLayoutPhase mPhase;
+ uint32_t mCount;
+};
+
+# define AUTO_LAYOUT_PHASE_ENTRY_POINT(pc_, phase_) \
+ nsAutoLayoutPhase autoLayoutPhase((pc_), (nsLayoutPhase::phase_))
+# define LAYOUT_PHASE_TEMP_EXIT() \
+ PR_BEGIN_MACRO \
+ autoLayoutPhase.Exit(); \
+ PR_END_MACRO
+# define LAYOUT_PHASE_TEMP_REENTER() \
+ PR_BEGIN_MACRO \
+ autoLayoutPhase.Enter(); \
+ PR_END_MACRO
+
+#else // DEBUG
+
+# define AUTO_LAYOUT_PHASE_ENTRY_POINT(pc_, phase_) PR_BEGIN_MACRO PR_END_MACRO
+# define LAYOUT_PHASE_TEMP_EXIT() PR_BEGIN_MACRO PR_END_MACRO
+# define LAYOUT_PHASE_TEMP_REENTER() PR_BEGIN_MACRO PR_END_MACRO
+
+#endif // DEBUG
+
+#endif // nsAutoLayoutPhase_h
diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp
new file mode 100644
index 0000000000..b1215972c2
--- /dev/null
+++ b/layout/base/nsBidiPresUtils.cpp
@@ -0,0 +1,2499 @@
+/* -*- 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 "nsBidiPresUtils.h"
+
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/Casting.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/dom/Text.h"
+
+#include "gfxContext.h"
+#include "nsFontMetrics.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "nsBidiUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsContainerFrame.h"
+#include "nsInlineFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPointerHashKeys.h"
+#include "nsFirstLetterFrame.h"
+#include "nsUnicodeProperties.h"
+#include "nsTextFrame.h"
+#include "nsBlockFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsStyleStructInlines.h"
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include <algorithm>
+
+#undef NOISY_BIDI
+#undef REALLY_NOISY_BIDI
+
+using namespace mozilla;
+
+using BidiEngine = intl::Bidi;
+using BidiClass = intl::BidiClass;
+using BidiDirection = intl::BidiDirection;
+using BidiEmbeddingLevel = intl::BidiEmbeddingLevel;
+
+static const char16_t kSpace = 0x0020;
+static const char16_t kZWSP = 0x200B;
+static const char16_t kLineSeparator = 0x2028;
+static const char16_t kObjectSubstitute = 0xFFFC;
+static const char16_t kLRE = 0x202A;
+static const char16_t kRLE = 0x202B;
+static const char16_t kLRO = 0x202D;
+static const char16_t kRLO = 0x202E;
+static const char16_t kPDF = 0x202C;
+static const char16_t kLRI = 0x2066;
+static const char16_t kRLI = 0x2067;
+static const char16_t kFSI = 0x2068;
+static const char16_t kPDI = 0x2069;
+// All characters with Bidi type Segment Separator or Block Separator
+static const char16_t kSeparators[] = {
+ char16_t('\t'), char16_t('\r'), char16_t('\n'), char16_t(0xb),
+ char16_t(0x1c), char16_t(0x1d), char16_t(0x1e), char16_t(0x1f),
+ char16_t(0x85), char16_t(0x2029), char16_t(0)};
+
+#define NS_BIDI_CONTROL_FRAME ((nsIFrame*)0xfffb1d1)
+
+// This exists just to be a type; the value doesn't matter.
+enum class BidiControlFrameType { Value };
+
+static bool IsIsolateControl(char16_t aChar) {
+ return aChar == kLRI || aChar == kRLI || aChar == kFSI;
+}
+
+// Given a ComputedStyle, return any bidi control character necessary to
+// implement style properties that override directionality (i.e. if it has
+// unicode-bidi:bidi-override, or text-orientation:upright in vertical
+// writing mode) when applying the bidi algorithm.
+//
+// Returns 0 if no override control character is implied by this style.
+static char16_t GetBidiOverride(ComputedStyle* aComputedStyle) {
+ const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
+ if ((vis->mWritingMode == StyleWritingModeProperty::VerticalRl ||
+ vis->mWritingMode == StyleWritingModeProperty::VerticalLr) &&
+ vis->mTextOrientation == StyleTextOrientation::Upright) {
+ return kLRO;
+ }
+ const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
+ if (text->mUnicodeBidi == StyleUnicodeBidi::BidiOverride ||
+ text->mUnicodeBidi == StyleUnicodeBidi::IsolateOverride) {
+ return StyleDirection::Rtl == vis->mDirection ? kRLO : kLRO;
+ }
+ return 0;
+}
+
+// Given a ComputedStyle, return any bidi control character necessary to
+// implement style properties that affect bidi resolution (i.e. if it
+// has unicode-bidiembed, isolate, or plaintext) when applying the bidi
+// algorithm.
+//
+// Returns 0 if no control character is implied by the style.
+//
+// Note that GetBidiOverride and GetBidiControl need to be separate
+// because in the case of unicode-bidi:isolate-override we need both
+// FSI and LRO/RLO.
+static char16_t GetBidiControl(ComputedStyle* aComputedStyle) {
+ const nsStyleVisibility* vis = aComputedStyle->StyleVisibility();
+ const nsStyleTextReset* text = aComputedStyle->StyleTextReset();
+ switch (text->mUnicodeBidi) {
+ case StyleUnicodeBidi::Embed:
+ return StyleDirection::Rtl == vis->mDirection ? kRLE : kLRE;
+ case StyleUnicodeBidi::Isolate:
+ // <bdi> element already has its directionality set from content so
+ // we never need to return kFSI.
+ return StyleDirection::Rtl == vis->mDirection ? kRLI : kLRI;
+ case StyleUnicodeBidi::IsolateOverride:
+ case StyleUnicodeBidi::Plaintext:
+ return kFSI;
+ case StyleUnicodeBidi::Normal:
+ case StyleUnicodeBidi::BidiOverride:
+ break;
+ }
+
+ return 0;
+}
+
+#ifdef DEBUG
+static inline bool AreContinuationsInOrder(nsIFrame* aFrame1,
+ nsIFrame* aFrame2) {
+ nsIFrame* f = aFrame1;
+ do {
+ f = f->GetNextContinuation();
+ } while (f && f != aFrame2);
+ return !!f;
+}
+#endif
+
+struct MOZ_STACK_CLASS BidiParagraphData {
+ struct FrameInfo {
+ FrameInfo(nsIFrame* aFrame, nsBlockInFlowLineIterator& aLineIter)
+ : mFrame(aFrame),
+ mBlockContainer(aLineIter.GetContainer()),
+ mInOverflow(aLineIter.GetInOverflow()) {}
+
+ explicit FrameInfo(BidiControlFrameType aValue)
+ : mFrame(NS_BIDI_CONTROL_FRAME),
+ mBlockContainer(nullptr),
+ mInOverflow(false) {}
+
+ FrameInfo()
+ : mFrame(nullptr), mBlockContainer(nullptr), mInOverflow(false) {}
+
+ nsIFrame* mFrame;
+
+ // The block containing mFrame (i.e., which continuation).
+ nsBlockFrame* mBlockContainer;
+
+ // true if mFrame is in mBlockContainer's overflow lines, false if
+ // in primary lines
+ bool mInOverflow;
+ };
+
+ nsAutoString mBuffer;
+ AutoTArray<char16_t, 16> mEmbeddingStack;
+ AutoTArray<FrameInfo, 16> mLogicalFrames;
+ nsTHashMap<nsPtrHashKey<const nsIContent>, int32_t> mContentToFrameIndex;
+ // Cached presentation context for the frames we're processing.
+ nsPresContext* mPresContext;
+ bool mIsVisual;
+ bool mRequiresBidi;
+ BidiEmbeddingLevel mParaLevel;
+ nsIContent* mPrevContent;
+
+ /**
+ * This class is designed to manage the process of mapping a frame to
+ * the line that it's in, when we know that (a) the frames we ask it
+ * about are always in the block's lines and (b) each successive frame
+ * we ask it about is the same as or after (in depth-first search
+ * order) the previous.
+ *
+ * Since we move through the lines at a different pace in Traverse and
+ * ResolveParagraph, we use one of these for each.
+ *
+ * The state of the mapping is also different between TraverseFrames
+ * and ResolveParagraph since since resolving can call functions
+ * (EnsureBidiContinuation or SplitInlineAncestors) that can create
+ * new frames and thus break lines.
+ *
+ * The TraverseFrames iterator is only used in some edge cases.
+ */
+ struct FastLineIterator {
+ FastLineIterator() : mPrevFrame(nullptr), mNextLineStart(nullptr) {}
+
+ // These iterators *and* mPrevFrame track the line list that we're
+ // iterating over.
+ //
+ // mPrevFrame, if non-null, should be either the frame we're currently
+ // handling (in ResolveParagraph or TraverseFrames, depending on the
+ // iterator) or a frame before it, and is also guaranteed to either be in
+ // mCurrentLine or have been in mCurrentLine until recently.
+ //
+ // In case the splitting causes block frames to break lines, however, we
+ // also track the first frame of the next line. If that changes, it means
+ // we've broken lines and we have to invalidate mPrevFrame.
+ nsBlockInFlowLineIterator mLineIterator;
+ nsIFrame* mPrevFrame;
+ nsIFrame* mNextLineStart;
+
+ nsLineList::iterator GetLine() { return mLineIterator.GetLine(); }
+
+ static bool IsFrameInCurrentLine(nsBlockInFlowLineIterator* aLineIter,
+ nsIFrame* aPrevFrame, nsIFrame* aFrame) {
+ MOZ_ASSERT(!aPrevFrame || aLineIter->GetLine()->Contains(aPrevFrame),
+ "aPrevFrame must be in aLineIter's current line");
+ nsIFrame* endFrame = aLineIter->IsLastLineInList()
+ ? nullptr
+ : aLineIter->GetLine().next()->mFirstChild;
+ nsIFrame* startFrame =
+ aPrevFrame ? aPrevFrame : aLineIter->GetLine()->mFirstChild;
+ for (nsIFrame* frame = startFrame; frame && frame != endFrame;
+ frame = frame->GetNextSibling()) {
+ if (frame == aFrame) return true;
+ }
+ return false;
+ }
+
+ static nsIFrame* FirstChildOfNextLine(
+ nsBlockInFlowLineIterator& aIterator) {
+ const nsLineList::iterator line = aIterator.GetLine();
+ const nsLineList::iterator lineEnd = aIterator.End();
+ MOZ_ASSERT(line != lineEnd, "iterator should start off valid");
+ const nsLineList::iterator nextLine = line.next();
+
+ return nextLine != lineEnd ? nextLine->mFirstChild : nullptr;
+ }
+
+ // Advance line iterator to the line containing aFrame, assuming
+ // that aFrame is already in the line list our iterator is iterating
+ // over.
+ void AdvanceToFrame(nsIFrame* aFrame) {
+ if (mPrevFrame && FirstChildOfNextLine(mLineIterator) != mNextLineStart) {
+ // Something has caused a line to split. We need to invalidate
+ // mPrevFrame since it may now be in a *later* line, though it may
+ // still be in this line, so we need to start searching for it from
+ // the start of this line.
+ mPrevFrame = nullptr;
+ }
+ nsIFrame* child = aFrame;
+ nsIFrame* parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ while (parent && !parent->IsBlockFrameOrSubclass()) {
+ child = parent;
+ parent = nsLayoutUtils::GetParentOrPlaceholderFor(child);
+ }
+ MOZ_ASSERT(parent, "aFrame is not a descendent of a block frame");
+ while (!IsFrameInCurrentLine(&mLineIterator, mPrevFrame, child)) {
+#ifdef DEBUG
+ bool hasNext =
+#endif
+ mLineIterator.Next();
+ MOZ_ASSERT(hasNext, "Can't find frame in lines!");
+ mPrevFrame = nullptr;
+ }
+ mPrevFrame = child;
+ mNextLineStart = FirstChildOfNextLine(mLineIterator);
+ }
+
+ // Advance line iterator to the line containing aFrame, which may
+ // require moving forward into overflow lines or into a later
+ // continuation (or both).
+ void AdvanceToLinesAndFrame(const FrameInfo& aFrameInfo) {
+ if (mLineIterator.GetContainer() != aFrameInfo.mBlockContainer ||
+ mLineIterator.GetInOverflow() != aFrameInfo.mInOverflow) {
+ MOZ_ASSERT(
+ mLineIterator.GetContainer() == aFrameInfo.mBlockContainer
+ ? (!mLineIterator.GetInOverflow() && aFrameInfo.mInOverflow)
+ : (!mLineIterator.GetContainer() ||
+ AreContinuationsInOrder(mLineIterator.GetContainer(),
+ aFrameInfo.mBlockContainer)),
+ "must move forwards");
+ nsBlockFrame* block = aFrameInfo.mBlockContainer;
+ nsLineList::iterator lines =
+ aFrameInfo.mInOverflow ? block->GetOverflowLines()->mLines.begin()
+ : block->LinesBegin();
+ mLineIterator =
+ nsBlockInFlowLineIterator(block, lines, aFrameInfo.mInOverflow);
+ mPrevFrame = nullptr;
+ }
+ AdvanceToFrame(aFrameInfo.mFrame);
+ }
+ };
+
+ FastLineIterator mCurrentTraverseLine, mCurrentResolveLine;
+
+#ifdef DEBUG
+ // Only used for NOISY debug output.
+ // Matches the current TraverseFrames state, not the ResolveParagraph
+ // state.
+ nsBlockFrame* mCurrentBlock;
+#endif
+
+ explicit BidiParagraphData(nsBlockFrame* aBlockFrame)
+ : mPresContext(aBlockFrame->PresContext()),
+ mIsVisual(mPresContext->IsVisualMode()),
+ mRequiresBidi(false),
+ mParaLevel(nsBidiPresUtils::BidiLevelFromStyle(aBlockFrame->Style())),
+ mPrevContent(nullptr)
+#ifdef DEBUG
+ ,
+ mCurrentBlock(aBlockFrame)
+#endif
+ {
+ if (mParaLevel > 0) {
+ mRequiresBidi = true;
+ }
+
+ if (mIsVisual) {
+ /**
+ * Drill up in content to detect whether this is an element that needs to
+ * be rendered with logical order even on visual pages.
+ *
+ * We always use logical order on form controls, firstly so that text
+ * entry will be in logical order, but also because visual pages were
+ * written with the assumption that even if the browser had no support
+ * for right-to-left text rendering, it would use native widgets with
+ * bidi support to display form controls.
+ *
+ * We also use logical order in XUL elements, since we expect that if a
+ * XUL element appears in a visual page, it will be generated by an XBL
+ * binding and contain localized text which will be in logical order.
+ */
+ for (nsIContent* content = aBlockFrame->GetContent(); content;
+ content = content->GetParent()) {
+ if (content->IsXULElement() || content->IsHTMLFormControlElement()) {
+ mIsVisual = false;
+ break;
+ }
+ }
+ }
+ }
+
+ nsresult SetPara() {
+ if (mPresContext->BidiEngine().SetParagraph(mBuffer, mParaLevel).isErr()) {
+ return NS_ERROR_FAILURE;
+ };
+ return NS_OK;
+ }
+
+ /**
+ * mParaLevel can be BidiDirection::LTR as well as
+ * BidiDirection::LTR or BidiDirection::RTL.
+ * GetParagraphEmbeddingLevel() returns the actual (resolved) paragraph level
+ * which is always either BidiDirection::LTR or
+ * BidiDirection::RTL
+ */
+ BidiEmbeddingLevel GetParagraphEmbeddingLevel() {
+ BidiEmbeddingLevel paraLevel = mParaLevel;
+ if (paraLevel == BidiEmbeddingLevel::DefaultLTR() ||
+ paraLevel == BidiEmbeddingLevel::DefaultRTL()) {
+ paraLevel = mPresContext->BidiEngine().GetParagraphEmbeddingLevel();
+ }
+ return paraLevel;
+ }
+
+ BidiEngine::ParagraphDirection GetParagraphDirection() {
+ return mPresContext->BidiEngine().GetParagraphDirection();
+ }
+
+ nsresult CountRuns(int32_t* runCount) {
+ auto result = mPresContext->BidiEngine().CountRuns();
+ if (result.isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+ *runCount = result.unwrap();
+ return NS_OK;
+ }
+
+ void GetLogicalRun(int32_t aLogicalStart, int32_t* aLogicalLimit,
+ BidiEmbeddingLevel* aLevel) {
+ mPresContext->BidiEngine().GetLogicalRun(aLogicalStart, aLogicalLimit,
+ aLevel);
+ if (mIsVisual) {
+ *aLevel = GetParagraphEmbeddingLevel();
+ }
+ }
+
+ void ResetData() {
+ mLogicalFrames.Clear();
+ mContentToFrameIndex.Clear();
+ mBuffer.SetLength(0);
+ mPrevContent = nullptr;
+ for (uint32_t i = 0; i < mEmbeddingStack.Length(); ++i) {
+ mBuffer.Append(mEmbeddingStack[i]);
+ mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
+ }
+ }
+
+ void AppendFrame(nsIFrame* aFrame, FastLineIterator& aLineIter,
+ nsIContent* aContent = nullptr) {
+ if (aContent) {
+ mContentToFrameIndex.InsertOrUpdate(aContent, FrameCount());
+ }
+
+ // We don't actually need to advance aLineIter to aFrame, since all we use
+ // from it is the block and is-overflow state, which are correct already.
+ mLogicalFrames.AppendElement(FrameInfo(aFrame, aLineIter.mLineIterator));
+ }
+
+ void AdvanceAndAppendFrame(nsIFrame** aFrame, FastLineIterator& aLineIter,
+ nsIFrame** aNextSibling) {
+ nsIFrame* frame = *aFrame;
+ nsIFrame* nextSibling = *aNextSibling;
+
+ frame = frame->GetNextContinuation();
+ if (frame) {
+ AppendFrame(frame, aLineIter, nullptr);
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+ }
+
+ *aFrame = frame;
+ *aNextSibling = nextSibling;
+ }
+
+ int32_t GetLastFrameForContent(nsIContent* aContent) {
+ return mContentToFrameIndex.Get(aContent);
+ }
+
+ int32_t FrameCount() { return mLogicalFrames.Length(); }
+
+ int32_t BufferLength() { return mBuffer.Length(); }
+
+ nsIFrame* FrameAt(int32_t aIndex) { return mLogicalFrames[aIndex].mFrame; }
+
+ const FrameInfo& FrameInfoAt(int32_t aIndex) {
+ return mLogicalFrames[aIndex];
+ }
+
+ void AppendUnichar(char16_t aCh) { mBuffer.Append(aCh); }
+
+ void AppendString(const nsDependentSubstring& aString) {
+ mBuffer.Append(aString);
+ }
+
+ void AppendControlChar(char16_t aCh) {
+ mLogicalFrames.AppendElement(FrameInfo(BidiControlFrameType::Value));
+ AppendUnichar(aCh);
+ }
+
+ void PushBidiControl(char16_t aCh) {
+ AppendControlChar(aCh);
+ mEmbeddingStack.AppendElement(aCh);
+ }
+
+ void AppendPopChar(char16_t aCh) {
+ AppendControlChar(IsIsolateControl(aCh) ? kPDI : kPDF);
+ }
+
+ void PopBidiControl(char16_t aCh) {
+ MOZ_ASSERT(mEmbeddingStack.Length(), "embedding/override underflow");
+ MOZ_ASSERT(aCh == mEmbeddingStack.LastElement());
+ AppendPopChar(aCh);
+ mEmbeddingStack.RemoveLastElement();
+ }
+
+ void ClearBidiControls() {
+ for (char16_t c : Reversed(mEmbeddingStack)) {
+ AppendPopChar(c);
+ }
+ }
+};
+
+struct MOZ_STACK_CLASS BidiLineData {
+ AutoTArray<nsIFrame*, 16> mLogicalFrames;
+ AutoTArray<nsIFrame*, 16> mVisualFrames;
+ AutoTArray<int32_t, 16> mIndexMap;
+ AutoTArray<BidiEmbeddingLevel, 16> mLevels;
+ bool mIsReordered;
+
+ BidiLineData(nsIFrame* aFirstFrameOnLine, int32_t aNumFramesOnLine) {
+ /**
+ * Initialize the logically-ordered array of frames using the top-level
+ * frames of a single line
+ */
+ bool isReordered = false;
+ bool hasRTLFrames = false;
+ bool hasVirtualControls = false;
+
+ auto appendFrame = [&](nsIFrame* frame, BidiEmbeddingLevel level) {
+ mLogicalFrames.AppendElement(frame);
+ mLevels.AppendElement(level);
+ mIndexMap.AppendElement(0);
+ if (level.IsRTL()) {
+ hasRTLFrames = true;
+ }
+ };
+
+ bool firstFrame = true;
+ for (nsIFrame* frame = aFirstFrameOnLine; frame && aNumFramesOnLine--;
+ frame = frame->GetNextSibling()) {
+ FrameBidiData bidiData = nsBidiPresUtils::GetFrameBidiData(frame);
+ // Ignore virtual control before the first frame. Doing so should
+ // not affect the visual result, but could avoid running into the
+ // stripping code below for many cases.
+ if (!firstFrame && bidiData.precedingControl != kBidiLevelNone) {
+ appendFrame(NS_BIDI_CONTROL_FRAME, bidiData.precedingControl);
+ hasVirtualControls = true;
+ }
+ appendFrame(frame, bidiData.embeddingLevel);
+ firstFrame = false;
+ }
+
+ // Reorder the line
+ BidiEngine::ReorderVisual(mLevels.Elements(), FrameCount(),
+ mIndexMap.Elements());
+
+ // Strip virtual frames
+ if (hasVirtualControls) {
+ auto originalCount = mLogicalFrames.Length();
+ AutoTArray<int32_t, 16> realFrameMap;
+ realFrameMap.SetCapacity(originalCount);
+ size_t count = 0;
+ for (auto i : IntegerRange(originalCount)) {
+ if (mLogicalFrames[i] == NS_BIDI_CONTROL_FRAME) {
+ realFrameMap.AppendElement(-1);
+ } else {
+ mLogicalFrames[count] = mLogicalFrames[i];
+ mLevels[count] = mLevels[i];
+ realFrameMap.AppendElement(count);
+ count++;
+ }
+ }
+ // Only keep index map for real frames.
+ for (size_t i = 0, j = 0; i < originalCount; ++i) {
+ auto newIndex = realFrameMap[mIndexMap[i]];
+ if (newIndex != -1) {
+ mIndexMap[j] = newIndex;
+ j++;
+ }
+ }
+ mLogicalFrames.TruncateLength(count);
+ mLevels.TruncateLength(count);
+ mIndexMap.TruncateLength(count);
+ }
+
+ for (int32_t i = 0; i < FrameCount(); i++) {
+ mVisualFrames.AppendElement(LogicalFrameAt(mIndexMap[i]));
+ if (i != mIndexMap[i]) {
+ isReordered = true;
+ }
+ }
+
+ // If there's an RTL frame, assume the line is reordered
+ mIsReordered = isReordered || hasRTLFrames;
+ }
+
+ int32_t FrameCount() const { return mLogicalFrames.Length(); }
+
+ nsIFrame* LogicalFrameAt(int32_t aIndex) const {
+ return mLogicalFrames[aIndex];
+ }
+
+ nsIFrame* VisualFrameAt(int32_t aIndex) const {
+ return mVisualFrames[aIndex];
+ }
+};
+
+#ifdef DEBUG
+extern "C" {
+void MOZ_EXPORT DumpFrameArray(const nsTArray<nsIFrame*>& aFrames) {
+ for (nsIFrame* frame : aFrames) {
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ fprintf_stderr(stderr, "(Bidi control frame)\n");
+ } else {
+ frame->List();
+ }
+ }
+}
+
+void MOZ_EXPORT DumpBidiLine(BidiLineData* aData, bool aVisualOrder) {
+ DumpFrameArray(aVisualOrder ? aData->mVisualFrames : aData->mLogicalFrames);
+}
+}
+#endif
+
+/* Some helper methods for Resolve() */
+
+// Should this frame be split between text runs?
+static bool IsBidiSplittable(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ // Bidi inline containers should be split, unless they're line frames.
+ LayoutFrameType frameType = aFrame->Type();
+ return (aFrame->IsBidiInlineContainer() &&
+ frameType != LayoutFrameType::Line) ||
+ frameType == LayoutFrameType::Text;
+}
+
+// Should this frame be treated as a leaf (e.g. when building mLogicalFrames)?
+static bool IsBidiLeaf(const nsIFrame* aFrame) {
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+ if (kid) {
+ if (aFrame->IsBidiInlineContainer() ||
+ RubyUtils::IsRubyBox(aFrame->Type())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Create non-fluid continuations for the ancestors of a given frame all the way
+ * up the frame tree until we hit a non-splittable frame (a line or a block).
+ *
+ * @param aParent the first parent frame to be split
+ * @param aFrame the child frames after this frame are reparented to the
+ * newly-created continuation of aParent.
+ * If aFrame is null, all the children of aParent are reparented.
+ */
+static void SplitInlineAncestors(nsContainerFrame* aParent,
+ nsLineList::iterator aLine, nsIFrame* aFrame) {
+ PresShell* presShell = aParent->PresShell();
+ nsIFrame* frame = aFrame;
+ nsContainerFrame* parent = aParent;
+ nsContainerFrame* newParent;
+
+ while (IsBidiSplittable(parent)) {
+ nsContainerFrame* grandparent = parent->GetParent();
+ NS_ASSERTION(grandparent,
+ "Couldn't get parent's parent in "
+ "nsBidiPresUtils::SplitInlineAncestors");
+
+ // Split the child list after |frame|, unless it is the last child.
+ if (!frame || frame->GetNextSibling()) {
+ newParent = static_cast<nsContainerFrame*>(
+ presShell->FrameConstructor()->CreateContinuingFrame(
+ parent, grandparent, false));
+
+ nsFrameList tail = parent->StealFramesAfter(frame);
+
+ // Reparent views as necessary
+ nsContainerFrame::ReparentFrameViewList(tail, parent, newParent);
+
+ // The parent's continuation adopts the siblings after the split.
+ MOZ_ASSERT(!newParent->IsBlockFrameOrSubclass(),
+ "blocks should not be IsBidiSplittable");
+ newParent->InsertFrames(FrameChildListID::NoReflowPrincipal, nullptr,
+ nullptr, std::move(tail));
+
+ // While passing &aLine to InsertFrames for a non-block isn't harmful
+ // because it's a no-op, it doesn't really make sense. However, the
+ // MOZ_ASSERT() we need to guarantee that it's safe only works if the
+ // parent is actually the block.
+ const nsLineList::iterator* parentLine;
+ if (grandparent->IsBlockFrameOrSubclass()) {
+ MOZ_ASSERT(aLine->Contains(parent));
+ parentLine = &aLine;
+ } else {
+ parentLine = nullptr;
+ }
+
+ // The list name FrameChildListID::NoReflowPrincipal would indicate we
+ // don't want reflow
+ grandparent->InsertFrames(FrameChildListID::NoReflowPrincipal, parent,
+ parentLine, nsFrameList(newParent, newParent));
+ }
+
+ frame = parent;
+ parent = grandparent;
+ }
+}
+
+static void MakeContinuationFluid(nsIFrame* aFrame, nsIFrame* aNext) {
+ NS_ASSERTION(!aFrame->GetNextInFlow() || aFrame->GetNextInFlow() == aNext,
+ "next-in-flow is not next continuation!");
+ aFrame->SetNextInFlow(aNext);
+
+ NS_ASSERTION(!aNext->GetPrevInFlow() || aNext->GetPrevInFlow() == aFrame,
+ "prev-in-flow is not prev continuation!");
+ aNext->SetPrevInFlow(aFrame);
+}
+
+static void MakeContinuationsNonFluidUpParentChain(nsIFrame* aFrame,
+ nsIFrame* aNext) {
+ nsIFrame* frame;
+ nsIFrame* next;
+
+ for (frame = aFrame, next = aNext;
+ frame && next && next != frame && next == frame->GetNextInFlow() &&
+ IsBidiSplittable(frame);
+ frame = frame->GetParent(), next = next->GetParent()) {
+ frame->SetNextContinuation(next);
+ next->SetPrevContinuation(frame);
+ }
+}
+
+// If aFrame is the last child of its parent, convert bidi continuations to
+// fluid continuations for all of its inline ancestors.
+// If it isn't the last child, make sure that its continuation is fluid.
+static void JoinInlineAncestors(nsIFrame* aFrame) {
+ nsIFrame* frame = aFrame;
+ while (frame && IsBidiSplittable(frame)) {
+ nsIFrame* next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ }
+ // Join the parent only as long as we're its last child.
+ if (frame->GetNextSibling()) break;
+ frame = frame->GetParent();
+ }
+}
+
+static void CreateContinuation(nsIFrame* aFrame,
+ const nsLineList::iterator aLine,
+ nsIFrame** aNewFrame, bool aIsFluid) {
+ MOZ_ASSERT(aNewFrame, "null OUT ptr");
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ *aNewFrame = nullptr;
+
+ nsPresContext* presContext = aFrame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ NS_ASSERTION(presShell,
+ "PresShell must be set on PresContext before calling "
+ "nsBidiPresUtils::CreateContinuation");
+
+ nsContainerFrame* parent = aFrame->GetParent();
+ NS_ASSERTION(
+ parent,
+ "Couldn't get frame parent in nsBidiPresUtils::CreateContinuation");
+
+ // While passing &aLine to InsertFrames for a non-block isn't harmful
+ // because it's a no-op, it doesn't really make sense. However, the
+ // MOZ_ASSERT() we need to guarantee that it's safe only works if the
+ // parent is actually the block.
+ const nsLineList::iterator* parentLine;
+ if (parent->IsBlockFrameOrSubclass()) {
+ MOZ_ASSERT(aLine->Contains(aFrame));
+ parentLine = &aLine;
+ } else {
+ parentLine = nullptr;
+ }
+
+ // Have to special case floating first letter frames because the continuation
+ // doesn't go in the first letter frame. The continuation goes with the rest
+ // of the text that the first letter frame was made out of.
+ if (parent->IsLetterFrame() && parent->IsFloating()) {
+ nsFirstLetterFrame* letterFrame = do_QueryFrame(parent);
+ letterFrame->CreateContinuationForFloatingParent(aFrame, aNewFrame,
+ aIsFluid);
+ return;
+ }
+
+ *aNewFrame = presShell->FrameConstructor()->CreateContinuingFrame(
+ aFrame, parent, aIsFluid);
+
+ // The list name FrameChildListID::NoReflowPrincipal would indicate we don't
+ // want reflow
+ // XXXbz this needs higher-level framelist love
+ parent->InsertFrames(FrameChildListID::NoReflowPrincipal, aFrame, parentLine,
+ nsFrameList(*aNewFrame, *aNewFrame));
+
+ if (!aIsFluid) {
+ // Split inline ancestor frames
+ SplitInlineAncestors(parent, aLine, aFrame);
+ }
+}
+
+/*
+ * Overview of the implementation of Resolve():
+ *
+ * Walk through the descendants of aBlockFrame and build:
+ * * mLogicalFrames: an nsTArray of nsIFrame* pointers in logical order
+ * * mBuffer: an nsString containing a representation of
+ * the content of the frames.
+ * In the case of text frames, this is the actual text context of the
+ * frames, but some other elements are represented in a symbolic form which
+ * will make the Unicode Bidi Algorithm give the correct results.
+ * Bidi isolates, embeddings, and overrides set by CSS, <bdi>, or <bdo>
+ * elements are represented by the corresponding Unicode control characters.
+ * <br> elements are represented by U+2028 LINE SEPARATOR
+ * Other inline elements are represented by U+FFFC OBJECT REPLACEMENT
+ * CHARACTER
+ *
+ * Then pass mBuffer to the Bidi engine for resolving of embedding levels
+ * by nsBidi::SetPara() and division into directional runs by
+ * nsBidi::CountRuns().
+ *
+ * Finally, walk these runs in logical order using nsBidi::GetLogicalRun() and
+ * correlate them with the frames indexed in mLogicalFrames, setting the
+ * baseLevel and embeddingLevel properties according to the results returned
+ * by the Bidi engine.
+ *
+ * The rendering layer requires each text frame to contain text in only one
+ * direction, so we may need to call EnsureBidiContinuation() to split frames.
+ * We may also need to call RemoveBidiContinuation() to convert frames created
+ * by EnsureBidiContinuation() in previous reflows into fluid continuations.
+ */
+nsresult nsBidiPresUtils::Resolve(nsBlockFrame* aBlockFrame) {
+ BidiParagraphData bpd(aBlockFrame);
+
+ // Handle bidi-override being set on the block itself before calling
+ // TraverseFrames.
+ // No need to call GetBidiControl as well, because isolate and embed
+ // values of unicode-bidi property are redundant on block elements.
+ // unicode-bidi:plaintext on a block element is handled by block frame
+ // via using nsIFrame::GetWritingMode(nsIFrame*).
+ char16_t ch = GetBidiOverride(aBlockFrame->Style());
+ if (ch != 0) {
+ bpd.PushBidiControl(ch);
+ bpd.mRequiresBidi = true;
+ } else {
+ // If there are no unicode-bidi properties and no RTL characters in the
+ // block's content, then it is pure LTR and we can skip the rest of bidi
+ // resolution.
+ nsIContent* currContent = nullptr;
+ for (nsBlockFrame* block = aBlockFrame; block;
+ block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
+ block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ if (!bpd.mRequiresBidi &&
+ ChildListMayRequireBidi(block->PrincipalChildList().FirstChild(),
+ &currContent)) {
+ bpd.mRequiresBidi = true;
+ }
+ if (!bpd.mRequiresBidi) {
+ nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
+ if (overflowLines) {
+ if (ChildListMayRequireBidi(overflowLines->mFrames.FirstChild(),
+ &currContent)) {
+ bpd.mRequiresBidi = true;
+ }
+ }
+ }
+ }
+ if (!bpd.mRequiresBidi) {
+ return NS_OK;
+ }
+ }
+
+ for (nsBlockFrame* block = aBlockFrame; block;
+ block = static_cast<nsBlockFrame*>(block->GetNextContinuation())) {
+#ifdef DEBUG
+ bpd.mCurrentBlock = block;
+#endif
+ block->RemoveStateBits(NS_BLOCK_NEEDS_BIDI_RESOLUTION);
+ bpd.mCurrentTraverseLine.mLineIterator =
+ nsBlockInFlowLineIterator(block, block->LinesBegin());
+ bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
+ TraverseFrames(block->PrincipalChildList().FirstChild(), &bpd);
+ nsBlockFrame::FrameLines* overflowLines = block->GetOverflowLines();
+ if (overflowLines) {
+ bpd.mCurrentTraverseLine.mLineIterator =
+ nsBlockInFlowLineIterator(block, overflowLines->mLines.begin(), true);
+ bpd.mCurrentTraverseLine.mPrevFrame = nullptr;
+ TraverseFrames(overflowLines->mFrames.FirstChild(), &bpd);
+ }
+ }
+
+ if (ch != 0) {
+ bpd.PopBidiControl(ch);
+ }
+
+ return ResolveParagraph(&bpd);
+}
+
+nsresult nsBidiPresUtils::ResolveParagraph(BidiParagraphData* aBpd) {
+ if (aBpd->BufferLength() < 1) {
+ return NS_OK;
+ }
+ aBpd->mBuffer.ReplaceChar(kSeparators, kSpace);
+
+ int32_t runCount;
+
+ nsresult rv = aBpd->SetPara();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ BidiEmbeddingLevel embeddingLevel = aBpd->GetParagraphEmbeddingLevel();
+
+ rv = aBpd->CountRuns(&runCount);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t runLength = 0; // the length of the current run of text
+ int32_t logicalLimit = 0; // the end of the current run + 1
+ int32_t numRun = -1;
+ int32_t fragmentLength = 0; // the length of the current text frame
+ int32_t frameIndex = -1; // index to the frames in mLogicalFrames
+ int32_t frameCount = aBpd->FrameCount();
+ int32_t contentOffset = 0; // offset of current frame in its content node
+ bool isTextFrame = false;
+ nsIFrame* frame = nullptr;
+ BidiParagraphData::FrameInfo frameInfo;
+ nsIContent* content = nullptr;
+ int32_t contentTextLength = 0;
+
+#ifdef DEBUG
+# ifdef NOISY_BIDI
+ printf(
+ "Before Resolve(), mCurrentBlock=%p, mBuffer='%s', frameCount=%d, "
+ "runCount=%d\n",
+ (void*)aBpd->mCurrentBlock, NS_ConvertUTF16toUTF8(aBpd->mBuffer).get(),
+ frameCount, runCount);
+# ifdef REALLY_NOISY_BIDI
+ printf(" block frame tree=:\n");
+ aBpd->mCurrentBlock->List(stdout);
+# endif
+# endif
+#endif
+
+ if (runCount == 1 && frameCount == 1 &&
+ aBpd->GetParagraphDirection() == BidiEngine::ParagraphDirection::LTR &&
+ aBpd->GetParagraphEmbeddingLevel() == 0) {
+ // We have a single left-to-right frame in a left-to-right paragraph,
+ // without bidi isolation from the surrounding text.
+ // Make sure that the embedding level and base level frame properties aren't
+ // set (because if they are this frame used to have some other direction,
+ // so we can't do this optimization), and we're done.
+ nsIFrame* frame = aBpd->FrameAt(0);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ FrameBidiData bidiData = frame->GetBidiData();
+ if (!bidiData.embeddingLevel && !bidiData.baseLevel) {
+#ifdef DEBUG
+# ifdef NOISY_BIDI
+ printf("early return for single direction frame %p\n", (void*)frame);
+# endif
+#endif
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ return NS_OK;
+ }
+ }
+ }
+
+ BidiParagraphData::FrameInfo lastRealFrame;
+ BidiEmbeddingLevel lastEmbeddingLevel = kBidiLevelNone;
+ BidiEmbeddingLevel precedingControl = kBidiLevelNone;
+
+ auto storeBidiDataToFrame = [&]() {
+ FrameBidiData bidiData;
+ bidiData.embeddingLevel = embeddingLevel;
+ bidiData.baseLevel = aBpd->GetParagraphEmbeddingLevel();
+ // If a control character doesn't have a lower embedding level than
+ // both the preceding and the following frame, it isn't something
+ // needed for getting the correct result. This optimization should
+ // remove almost all of embeds and overrides, and some of isolates.
+ if (precedingControl >= embeddingLevel ||
+ precedingControl >= lastEmbeddingLevel) {
+ bidiData.precedingControl = kBidiLevelNone;
+ } else {
+ bidiData.precedingControl = precedingControl;
+ }
+ precedingControl = kBidiLevelNone;
+ lastEmbeddingLevel = embeddingLevel;
+ frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
+ };
+
+ for (;;) {
+ if (fragmentLength <= 0) {
+ // Get the next frame from mLogicalFrames
+ if (++frameIndex >= frameCount) {
+ break;
+ }
+ frameInfo = aBpd->FrameInfoAt(frameIndex);
+ frame = frameInfo.mFrame;
+ if (frame == NS_BIDI_CONTROL_FRAME || !frame->IsTextFrame()) {
+ /*
+ * Any non-text frame corresponds to a single character in the text
+ * buffer (a bidi control character, LINE SEPARATOR, or OBJECT
+ * SUBSTITUTE)
+ */
+ isTextFrame = false;
+ fragmentLength = 1;
+ } else {
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(frameInfo);
+ content = frame->GetContent();
+ if (!content) {
+ rv = NS_OK;
+ break;
+ }
+ contentTextLength = content->TextLength();
+ auto [start, end] = frame->GetOffsets();
+ NS_ASSERTION(!(contentTextLength < end - start),
+ "Frame offsets don't fit in content");
+ fragmentLength = std::min(contentTextLength, end - start);
+ contentOffset = start;
+ isTextFrame = true;
+ }
+ } // if (fragmentLength <= 0)
+
+ if (runLength <= 0) {
+ // Get the next run of text from the Bidi engine
+ if (++numRun >= runCount) {
+ // We've run out of runs of text; but don't forget to store bidi data
+ // to the frame before breaking out of the loop (bug 1426042).
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ storeBidiDataToFrame();
+ if (isTextFrame) {
+ frame->AdjustOffsetsForBidi(contentOffset,
+ contentOffset + fragmentLength);
+ }
+ }
+ break;
+ }
+ int32_t lineOffset = logicalLimit;
+ aBpd->GetLogicalRun(lineOffset, &logicalLimit, &embeddingLevel);
+ runLength = logicalLimit - lineOffset;
+ } // if (runLength <= 0)
+
+ if (frame == NS_BIDI_CONTROL_FRAME) {
+ // In theory, we only need to do this for isolates. However, it is
+ // easier to do this for all here because we do not maintain the
+ // index to get corresponding character from buffer. Since we do
+ // have proper embedding level for all those characters, including
+ // them wouldn't affect the final result.
+ precedingControl = std::min(precedingControl, embeddingLevel);
+ } else {
+ storeBidiDataToFrame();
+ if (isTextFrame) {
+ if (contentTextLength == 0) {
+ // Set the base level and embedding level of the current run even
+ // on an empty frame. Otherwise frame reordering will not be correct.
+ frame->AdjustOffsetsForBidi(0, 0);
+ // Nothing more to do for an empty frame, except update
+ // lastRealFrame like we do below.
+ lastRealFrame = frameInfo;
+ continue;
+ }
+ nsLineList::iterator currentLine = aBpd->mCurrentResolveLine.GetLine();
+ if ((runLength > 0) && (runLength < fragmentLength)) {
+ /*
+ * The text in this frame continues beyond the end of this directional
+ * run. Create a non-fluid continuation frame for the next directional
+ * run.
+ */
+ currentLine->MarkDirty();
+ nsIFrame* nextBidi;
+ int32_t runEnd = contentOffset + runLength;
+ EnsureBidiContinuation(frame, currentLine, &nextBidi, contentOffset,
+ runEnd);
+ nextBidi->AdjustOffsetsForBidi(runEnd,
+ contentOffset + fragmentLength);
+ frame = nextBidi;
+ frameInfo.mFrame = frame;
+ contentOffset = runEnd;
+
+ aBpd->mCurrentResolveLine.AdvanceToFrame(frame);
+ } // if (runLength < fragmentLength)
+ else {
+ if (contentOffset + fragmentLength == contentTextLength) {
+ /*
+ * We have finished all the text in this content node. Convert any
+ * further non-fluid continuations to fluid continuations and
+ * advance frameIndex to the last frame in the content node
+ */
+ int32_t newIndex = aBpd->GetLastFrameForContent(content);
+ if (newIndex > frameIndex) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ frameIndex = newIndex;
+ frameInfo = aBpd->FrameInfoAt(frameIndex);
+ frame = frameInfo.mFrame;
+ }
+ } else if (fragmentLength > 0 && runLength > fragmentLength) {
+ /*
+ * There is more text that belongs to this directional run in the
+ * next text frame: make sure it is a fluid continuation of the
+ * current frame. Do not advance frameIndex, because the next frame
+ * may contain multi-directional text and need to be split
+ */
+ int32_t newIndex = frameIndex;
+ do {
+ } while (++newIndex < frameCount &&
+ aBpd->FrameAt(newIndex) == NS_BIDI_CONTROL_FRAME);
+ if (newIndex < frameCount) {
+ currentLine->MarkDirty();
+ RemoveBidiContinuation(aBpd, frame, frameIndex, newIndex);
+ }
+ } else if (runLength == fragmentLength) {
+ /*
+ * If the directional run ends at the end of the frame, make sure
+ * that any continuation is non-fluid, and do the same up the
+ * parent chain
+ */
+ nsIFrame* next = frame->GetNextInFlow();
+ if (next) {
+ currentLine->MarkDirty();
+ MakeContinuationsNonFluidUpParentChain(frame, next);
+ }
+ }
+ frame->AdjustOffsetsForBidi(contentOffset,
+ contentOffset + fragmentLength);
+ }
+ } // isTextFrame
+ } // not bidi control frame
+ int32_t temp = runLength;
+ runLength -= fragmentLength;
+ fragmentLength -= temp;
+
+ // Record last real frame so that we can do splitting properly even
+ // if a run ends after a virtual bidi control frame.
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ lastRealFrame = frameInfo;
+ }
+ if (lastRealFrame.mFrame && fragmentLength <= 0) {
+ // If the frame is at the end of a run, and this is not the end of our
+ // paragraph, split all ancestor inlines that need splitting.
+ // To determine whether we're at the end of the run, we check that we've
+ // finished processing the current run, and that the current frame
+ // doesn't have a fluid continuation (it could have a fluid continuation
+ // of zero length, so testing runLength alone is not sufficient).
+ if (runLength <= 0 && !lastRealFrame.mFrame->GetNextInFlow()) {
+ if (numRun + 1 < runCount) {
+ nsIFrame* child = lastRealFrame.mFrame;
+ nsContainerFrame* parent = child->GetParent();
+ // As long as we're on the last sibling, the parent doesn't have to
+ // be split.
+ // However, if the parent has a fluid continuation, we do have to make
+ // it non-fluid. This can happen e.g. when we have a first-letter
+ // frame and the end of the first-letter coincides with the end of a
+ // directional run.
+ while (parent && IsBidiSplittable(parent) &&
+ !child->GetNextSibling()) {
+ nsIFrame* next = parent->GetNextInFlow();
+ if (next) {
+ parent->SetNextContinuation(next);
+ next->SetPrevContinuation(parent);
+ }
+ child = parent;
+ parent = child->GetParent();
+ }
+ if (parent && IsBidiSplittable(parent)) {
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
+ SplitInlineAncestors(parent, aBpd->mCurrentResolveLine.GetLine(),
+ child);
+
+ aBpd->mCurrentResolveLine.AdvanceToLinesAndFrame(lastRealFrame);
+ }
+ }
+ } else if (frame != NS_BIDI_CONTROL_FRAME) {
+ // We're not at an end of a run. If |frame| is the last child of its
+ // parent, and its ancestors happen to have bidi continuations, convert
+ // them into fluid continuations.
+ JoinInlineAncestors(frame);
+ }
+ }
+ } // for
+
+#ifdef DEBUG
+# ifdef REALLY_NOISY_BIDI
+ printf("---\nAfter Resolve(), frameTree =:\n");
+ aBpd->mCurrentBlock->List(stdout);
+ printf("===\n");
+# endif
+#endif
+
+ return rv;
+}
+
+void nsBidiPresUtils::TraverseFrames(nsIFrame* aCurrentFrame,
+ BidiParagraphData* aBpd) {
+ if (!aCurrentFrame) return;
+
+#ifdef DEBUG
+ nsBlockFrame* initialLineContainer =
+ aBpd->mCurrentTraverseLine.mLineIterator.GetContainer();
+#endif
+
+ nsIFrame* childFrame = aCurrentFrame;
+ do {
+ /*
+ * It's important to get the next sibling and next continuation *before*
+ * handling the frame: If we encounter a forced paragraph break and call
+ * ResolveParagraph within this loop, doing GetNextSibling and
+ * GetNextContinuation after that could return a bidi continuation that had
+ * just been split from the original childFrame and we would process it
+ * twice.
+ */
+ nsIFrame* nextSibling = childFrame->GetNextSibling();
+
+ // If the real frame for a placeholder is a first letter frame, we need to
+ // drill down into it and include its contents in Bidi resolution.
+ // If not, we just use the placeholder.
+ nsIFrame* frame = childFrame;
+ if (childFrame->IsPlaceholderFrame()) {
+ nsIFrame* realFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
+ if (realFrame->IsLetterFrame()) {
+ frame = realFrame;
+ }
+ }
+
+ auto DifferentBidiValues = [](ComputedStyle* aSC1, nsIFrame* aFrame2) {
+ ComputedStyle* sc2 = aFrame2->Style();
+ return GetBidiControl(aSC1) != GetBidiControl(sc2) ||
+ GetBidiOverride(aSC1) != GetBidiOverride(sc2);
+ };
+
+ ComputedStyle* sc = frame->Style();
+ nsIFrame* nextContinuation = frame->GetNextContinuation();
+ nsIFrame* prevContinuation = frame->GetPrevContinuation();
+ bool isLastFrame =
+ !nextContinuation || DifferentBidiValues(sc, nextContinuation);
+ bool isFirstFrame =
+ !prevContinuation || DifferentBidiValues(sc, prevContinuation);
+
+ char16_t controlChar = 0;
+ char16_t overrideChar = 0;
+ LayoutFrameType frameType = frame->Type();
+ if (frame->IsBidiInlineContainer() || RubyUtils::IsRubyBox(frameType)) {
+ if (!frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ nsContainerFrame* c = static_cast<nsContainerFrame*>(frame);
+ MOZ_ASSERT(c == do_QueryFrame(frame),
+ "eBidiInlineContainer and ruby frame must be"
+ " a nsContainerFrame subclass");
+ c->DrainSelfOverflowList();
+ }
+
+ controlChar = GetBidiControl(sc);
+ overrideChar = GetBidiOverride(sc);
+
+ // Add dummy frame pointers representing bidi control codes before
+ // the first frames of elements specifying override, isolation, or
+ // plaintext.
+ if (isFirstFrame) {
+ if (controlChar != 0) {
+ aBpd->PushBidiControl(controlChar);
+ }
+ if (overrideChar != 0) {
+ aBpd->PushBidiControl(overrideChar);
+ }
+ }
+ }
+
+ if (IsBidiLeaf(frame)) {
+ /* Bidi leaf frame: add the frame to the mLogicalFrames array,
+ * and add its index to the mContentToFrameIndex hashtable. This
+ * will be used in RemoveBidiContinuation() to identify the last
+ * frame in the array with a given content.
+ */
+ nsIContent* content = frame->GetContent();
+ aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine, content);
+
+ // Append the content of the frame to the paragraph buffer
+ if (LayoutFrameType::Text == frameType) {
+ if (content != aBpd->mPrevContent) {
+ aBpd->mPrevContent = content;
+ if (!frame->StyleText()->NewlineIsSignificant(
+ static_cast<nsTextFrame*>(frame))) {
+ content->GetAsText()->AppendTextTo(aBpd->mBuffer);
+ } else {
+ /*
+ * For preformatted text we have to do bidi resolution on each line
+ * separately.
+ */
+ nsAutoString text;
+ content->GetAsText()->AppendTextTo(text);
+ nsIFrame* next;
+ do {
+ next = nullptr;
+
+ auto [start, end] = frame->GetOffsets();
+ int32_t endLine = text.FindChar('\n', start);
+ if (endLine == -1) {
+ /*
+ * If there is no newline in the text content, just save the
+ * text from this frame and its continuations, and do bidi
+ * resolution later
+ */
+ aBpd->AppendString(Substring(text, start));
+ while (frame && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(
+ &frame, aBpd->mCurrentTraverseLine, &nextSibling);
+ }
+ break;
+ }
+
+ /*
+ * If there is a newline in the frame, break the frame after the
+ * newline, do bidi resolution and repeat until the last sibling
+ */
+ ++endLine;
+
+ /*
+ * If the frame ends before the new line, save the text and move
+ * into the next continuation
+ */
+ aBpd->AppendString(
+ Substring(text, start, std::min(end, endLine) - start));
+ while (end < endLine && nextSibling) {
+ aBpd->AdvanceAndAppendFrame(&frame, aBpd->mCurrentTraverseLine,
+ &nextSibling);
+ NS_ASSERTION(frame, "Premature end of continuation chain");
+ std::tie(start, end) = frame->GetOffsets();
+ aBpd->AppendString(
+ Substring(text, start, std::min(end, endLine) - start));
+ }
+
+ if (end < endLine) {
+ aBpd->mPrevContent = nullptr;
+ break;
+ }
+
+ bool createdContinuation = false;
+ if (uint32_t(endLine) < text.Length()) {
+ /*
+ * Timing is everything here: if the frame already has a bidi
+ * continuation, we need to make the continuation fluid *before*
+ * resetting the length of the current frame. Otherwise
+ * nsTextFrame::SetLength won't set the continuation frame's
+ * text offsets correctly.
+ *
+ * On the other hand, if the frame doesn't have a continuation,
+ * we need to create one *after* resetting the length, or
+ * CreateContinuingFrame will complain that there is no more
+ * content for the continuation.
+ */
+ next = frame->GetNextInFlow();
+ if (!next) {
+ // If the frame already has a bidi continuation, make it fluid
+ next = frame->GetNextContinuation();
+ if (next) {
+ MakeContinuationFluid(frame, next);
+ JoinInlineAncestors(frame);
+ }
+ }
+
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(frame);
+ textFrame->SetLength(endLine - start, nullptr);
+
+ // If it weren't for CreateContinuation needing this to
+ // be current, we could restructure the marking dirty
+ // below to use mCurrentResolveLine and eliminate
+ // mCurrentTraverseLine entirely.
+ aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
+
+ if (!next) {
+ // If the frame has no next in flow, create one.
+ CreateContinuation(
+ frame, aBpd->mCurrentTraverseLine.GetLine(), &next, true);
+ createdContinuation = true;
+ }
+ // Mark the line before the newline as dirty.
+ aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
+ }
+ ResolveParagraphWithinBlock(aBpd);
+
+ if (!nextSibling && !createdContinuation) {
+ break;
+ }
+ if (next) {
+ frame = next;
+ aBpd->AppendFrame(frame, aBpd->mCurrentTraverseLine);
+ // Mark the line after the newline as dirty.
+ aBpd->mCurrentTraverseLine.AdvanceToFrame(frame);
+ aBpd->mCurrentTraverseLine.GetLine()->MarkDirty();
+ }
+
+ /*
+ * If we have already overshot the saved next-sibling while
+ * scanning the frame's continuations, advance it.
+ */
+ if (frame && frame == nextSibling) {
+ nextSibling = frame->GetNextSibling();
+ }
+
+ } while (next);
+ }
+ }
+ } else if (LayoutFrameType::Br == frameType) {
+ // break frame -- append line separator
+ aBpd->AppendUnichar(kLineSeparator);
+ ResolveParagraphWithinBlock(aBpd);
+ } else {
+ // other frame type -- see the Unicode Bidi Algorithm:
+ // "...inline objects (such as graphics) are treated as if they are ...
+ // U+FFFC"
+ // <wbr>, however, is treated as U+200B ZERO WIDTH SPACE. See
+ // http://dev.w3.org/html5/spec/Overview.html#phrasing-content-1
+ aBpd->AppendUnichar(
+ content->IsHTMLElement(nsGkAtoms::wbr) ? kZWSP : kObjectSubstitute);
+ if (!frame->IsInlineOutside()) {
+ // if it is not inline, end the paragraph
+ ResolveParagraphWithinBlock(aBpd);
+ }
+ }
+ } else {
+ // For a non-leaf frame, recurse into TraverseFrames
+ nsIFrame* kid = frame->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(!frame->GetChildList(FrameChildListID::Overflow).FirstChild(),
+ "should have drained the overflow list above");
+ if (kid) {
+ TraverseFrames(kid, aBpd);
+ }
+ }
+
+ // If the element is attributed by dir, indicate direction pop (add PDF
+ // frame)
+ if (isLastFrame) {
+ // Add a dummy frame pointer representing a bidi control code after the
+ // last frame of an element specifying embedding or override
+ if (overrideChar != 0) {
+ aBpd->PopBidiControl(overrideChar);
+ }
+ if (controlChar != 0) {
+ aBpd->PopBidiControl(controlChar);
+ }
+ }
+ childFrame = nextSibling;
+ } while (childFrame);
+
+ MOZ_ASSERT(initialLineContainer ==
+ aBpd->mCurrentTraverseLine.mLineIterator.GetContainer());
+}
+
+bool nsBidiPresUtils::ChildListMayRequireBidi(nsIFrame* aFirstChild,
+ nsIContent** aCurrContent) {
+ MOZ_ASSERT(!aFirstChild || !aFirstChild->GetPrevSibling(),
+ "Expecting to traverse from the start of a child list");
+
+ for (nsIFrame* childFrame = aFirstChild; childFrame;
+ childFrame = childFrame->GetNextSibling()) {
+ nsIFrame* frame = childFrame;
+
+ // If the real frame for a placeholder is a first-letter frame, we need to
+ // consider its contents for potential Bidi resolution.
+ if (childFrame->IsPlaceholderFrame()) {
+ nsIFrame* realFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(childFrame);
+ if (realFrame->IsLetterFrame()) {
+ frame = realFrame;
+ }
+ }
+
+ // If unicode-bidi properties are present, we should do bidi resolution.
+ ComputedStyle* sc = frame->Style();
+ if (GetBidiControl(sc) || GetBidiOverride(sc)) {
+ return true;
+ }
+
+ if (IsBidiLeaf(frame)) {
+ if (frame->IsTextFrame()) {
+ // If the frame already has a BidiDataProperty, we know we need to
+ // perform bidi resolution (even if no bidi content is NOW present --
+ // we might need to remove the property set by a previous reflow, if
+ // content has changed; see bug 1366623).
+ if (frame->HasProperty(nsIFrame::BidiDataProperty())) {
+ return true;
+ }
+
+ // Check whether the text frame has any RTL characters; if so, bidi
+ // resolution will be needed.
+ dom::Text* content = frame->GetContent()->AsText();
+ if (content != *aCurrContent) {
+ *aCurrContent = content;
+ const nsTextFragment* txt = &content->TextFragment();
+ if (txt->Is2b() &&
+ HasRTLChars(Span(txt->Get2b(), txt->GetLength()))) {
+ return true;
+ }
+ }
+ }
+ } else if (ChildListMayRequireBidi(frame->PrincipalChildList().FirstChild(),
+ aCurrContent)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsBidiPresUtils::ResolveParagraphWithinBlock(BidiParagraphData* aBpd) {
+ aBpd->ClearBidiControls();
+ ResolveParagraph(aBpd);
+ aBpd->ResetData();
+}
+
+/* static */
+nscoord nsBidiPresUtils::ReorderFrames(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart) {
+ nsSize containerSize(aContainerSize);
+
+ // If this line consists of a line frame, reorder the line frame's children.
+ if (aFirstFrameOnLine->IsLineFrame()) {
+ // The line frame is positioned at the start-edge, so use its size
+ // as the container size.
+ containerSize = aFirstFrameOnLine->GetSize();
+
+ aFirstFrameOnLine = aFirstFrameOnLine->PrincipalChildList().FirstChild();
+ if (!aFirstFrameOnLine) {
+ return 0;
+ }
+ // All children of the line frame are on the first line. Setting
+ // aNumFramesOnLine to -1 makes InitLogicalArrayFromLine look at all of
+ // them.
+ aNumFramesOnLine = -1;
+ // As the line frame itself has been adjusted at its inline-start position
+ // by the caller, we do not want to apply this to its children.
+ aStart = 0;
+ }
+
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ return RepositionInlineFrames(&bld, aLineWM, containerSize, aStart);
+}
+
+nsIFrame* nsBidiPresUtils::GetFirstLeaf(nsIFrame* aFrame) {
+ nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ nsIFrame* firstChild = firstLeaf->PrincipalChildList().FirstChild();
+ nsIFrame* realFrame = nsPlaceholderFrame::GetRealFrameFor(firstChild);
+ firstLeaf = (realFrame->IsLetterFrame()) ? realFrame : firstChild;
+ }
+ return firstLeaf;
+}
+
+FrameBidiData nsBidiPresUtils::GetFrameBidiData(nsIFrame* aFrame) {
+ return GetFirstLeaf(aFrame)->GetBidiData();
+}
+
+BidiEmbeddingLevel nsBidiPresUtils::GetFrameEmbeddingLevel(nsIFrame* aFrame) {
+ return GetFirstLeaf(aFrame)->GetEmbeddingLevel();
+}
+
+BidiEmbeddingLevel nsBidiPresUtils::GetFrameBaseLevel(const nsIFrame* aFrame) {
+ const nsIFrame* firstLeaf = aFrame;
+ while (!IsBidiLeaf(firstLeaf)) {
+ firstLeaf = firstLeaf->PrincipalChildList().FirstChild();
+ }
+ return firstLeaf->GetBaseLevel();
+}
+
+void nsBidiPresUtils::IsFirstOrLast(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates,
+ bool aSpanDirMatchesLineDir,
+ bool& aIsFirst /* out */,
+ bool& aIsLast /* out */) {
+ /*
+ * Since we lay out frames in the line's direction, visiting a frame with
+ * 'mFirstVisualFrame == nullptr', means it's the first appearance of one
+ * of its continuation chain frames on the line.
+ * To determine if it's the last visual frame of its continuation chain on
+ * the line or not, we count the number of frames of the chain on the line,
+ * and then reduce it when we lay out a frame of the chain. If this value
+ * becomes 1 it means that it's the last visual frame of its continuation
+ * chain on this line.
+ */
+
+ bool firstInLineOrder, lastInLineOrder;
+ nsFrameContinuationState* frameState = aContinuationStates->Get(aFrame);
+ nsFrameContinuationState* firstFrameState;
+
+ if (!frameState->mFirstVisualFrame) {
+ // aFrame is the first visual frame of its continuation chain
+ nsFrameContinuationState* contState;
+ nsIFrame* frame;
+
+ frameState->mFrameCount = 1;
+ frameState->mFirstVisualFrame = aFrame;
+
+ /**
+ * Traverse continuation chain of aFrame in both backward and forward
+ * directions while the frames are on this line. Count the frames and
+ * set their mFirstVisualFrame to aFrame.
+ */
+ // Traverse continuation chain backward
+ for (frame = aFrame->GetPrevContinuation();
+ frame && (contState = aContinuationStates->Get(frame));
+ frame = frame->GetPrevContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnPrevLines = (frame != nullptr);
+
+ // Traverse continuation chain forward
+ for (frame = aFrame->GetNextContinuation();
+ frame && (contState = aContinuationStates->Get(frame));
+ frame = frame->GetNextContinuation()) {
+ frameState->mFrameCount++;
+ contState->mFirstVisualFrame = aFrame;
+ }
+ frameState->mHasContOnNextLines = (frame != nullptr);
+
+ firstInLineOrder = true;
+ firstFrameState = frameState;
+ } else {
+ // aFrame is not the first visual frame of its continuation chain
+ firstInLineOrder = false;
+ firstFrameState = aContinuationStates->Get(frameState->mFirstVisualFrame);
+ }
+
+ lastInLineOrder = (firstFrameState->mFrameCount == 1);
+
+ if (aSpanDirMatchesLineDir) {
+ aIsFirst = firstInLineOrder;
+ aIsLast = lastInLineOrder;
+ } else {
+ aIsFirst = lastInLineOrder;
+ aIsLast = firstInLineOrder;
+ }
+
+ if (frameState->mHasContOnPrevLines) {
+ aIsFirst = false;
+ }
+ if (firstFrameState->mHasContOnNextLines) {
+ aIsLast = false;
+ }
+
+ if ((aIsFirst || aIsLast) &&
+ aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // For ib splits, don't treat anything except the last part as
+ // endmost or anything except the first part as startmost.
+ // As an optimization, only get the first continuation once.
+ nsIFrame* firstContinuation = aFrame->FirstContinuation();
+ if (firstContinuation->FrameIsNonLastInIBSplit()) {
+ // We are not endmost
+ aIsLast = false;
+ }
+ if (firstContinuation->FrameIsNonFirstInIBSplit()) {
+ // We are not startmost
+ aIsFirst = false;
+ }
+ }
+
+ // Reduce number of remaining frames of the continuation chain on the line.
+ firstFrameState->mFrameCount--;
+
+ nsInlineFrame* testFrame = do_QueryFrame(aFrame);
+
+ if (testFrame) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_STATE_IS_SET);
+
+ if (aIsFirst) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_FIRST);
+ }
+
+ if (aIsLast) {
+ aFrame->AddStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ } else {
+ aFrame->RemoveStateBits(NS_INLINE_FRAME_BIDI_VISUAL_IS_LAST);
+ }
+ }
+}
+
+/* static */
+void nsBidiPresUtils::RepositionRubyContentFrame(
+ nsIFrame* aFrame, WritingMode aFrameWM,
+ const LogicalMargin& aBorderPadding) {
+ const nsFrameList& childList = aFrame->PrincipalChildList();
+ if (childList.IsEmpty()) {
+ return;
+ }
+
+ // Reorder the children.
+ nscoord isize =
+ ReorderFrames(childList.FirstChild(), childList.GetLength(), aFrameWM,
+ aFrame->GetSize(), aBorderPadding.IStart(aFrameWM));
+ isize += aBorderPadding.IEnd(aFrameWM);
+
+ if (aFrame->StyleText()->mRubyAlign == StyleRubyAlign::Start) {
+ return;
+ }
+ nscoord residualISize = aFrame->ISize(aFrameWM) - isize;
+ if (residualISize <= 0) {
+ return;
+ }
+
+ // When ruby-align is not "start", if the content does not fill this
+ // frame, we need to center the children.
+ const nsSize dummyContainerSize;
+ for (nsIFrame* child : childList) {
+ LogicalRect rect = child->GetLogicalRect(aFrameWM, dummyContainerSize);
+ rect.IStart(aFrameWM) += residualISize / 2;
+ child->SetRect(aFrameWM, rect, dummyContainerSize);
+ }
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionRubyFrame(
+ nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
+ const WritingMode aContainerWM, const LogicalMargin& aBorderPadding) {
+ LayoutFrameType frameType = aFrame->Type();
+ MOZ_ASSERT(RubyUtils::IsRubyBox(frameType));
+
+ nscoord icoord = 0;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ bool isLTR = frameWM.IsBidiLTR();
+ nsSize frameSize = aFrame->GetSize();
+ if (frameType == LayoutFrameType::Ruby) {
+ icoord += aBorderPadding.IStart(frameWM);
+ // Reposition ruby segments in a ruby container
+ for (RubySegmentEnumerator e(static_cast<nsRubyFrame*>(aFrame)); !e.AtEnd();
+ e.Next()) {
+ nsRubyBaseContainerFrame* rbc = e.GetBaseContainer();
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ nscoord segmentISize = RepositionFrame(
+ rbc, isLTR, icoord, aContinuationStates, frameWM, false, frameSize);
+ for (nsRubyTextContainerFrame* rtc : textContainers) {
+ nscoord isize = RepositionFrame(rtc, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ segmentISize = std::max(segmentISize, isize);
+ }
+ icoord += segmentISize;
+ }
+ icoord += aBorderPadding.IEnd(frameWM);
+ } else if (frameType == LayoutFrameType::RubyBaseContainer) {
+ // Reposition ruby columns in a ruby segment
+ auto rbc = static_cast<nsRubyBaseContainerFrame*>(aFrame);
+ AutoRubyTextContainerArray textContainers(rbc);
+
+ for (RubyColumnEnumerator e(rbc, textContainers); !e.AtEnd(); e.Next()) {
+ RubyColumn column;
+ e.GetColumn(column);
+
+ nscoord columnISize =
+ RepositionFrame(column.mBaseFrame, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ for (nsRubyTextFrame* rt : column.mTextFrames) {
+ nscoord isize = RepositionFrame(rt, isLTR, icoord, aContinuationStates,
+ frameWM, false, frameSize);
+ columnISize = std::max(columnISize, isize);
+ }
+ icoord += columnISize;
+ }
+ } else {
+ if (frameType == LayoutFrameType::RubyBase ||
+ frameType == LayoutFrameType::RubyText) {
+ RepositionRubyContentFrame(aFrame, frameWM, aBorderPadding);
+ }
+ // Note that, ruby text container is not present in all conditions
+ // above. It is intended, because the children of rtc are reordered
+ // with the children of ruby base container simultaneously. We only
+ // need to return its isize here, as it should not be changed.
+ icoord += aFrame->ISize(aContainerWM);
+ }
+ return icoord;
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionFrame(
+ nsIFrame* aFrame, bool aIsEvenLevel, nscoord aStartOrEnd,
+ nsContinuationStates* aContinuationStates, WritingMode aContainerWM,
+ bool aContainerReverseDir, const nsSize& aContainerSize) {
+ nscoord lineSize =
+ aContainerWM.IsVertical() ? aContainerSize.height : aContainerSize.width;
+ NS_ASSERTION(lineSize != NS_UNCONSTRAINEDSIZE,
+ "Unconstrained inline line size in bidi frame reordering");
+ if (!aFrame) return 0;
+
+ bool isFirst, isLast;
+ WritingMode frameWM = aFrame->GetWritingMode();
+ IsFirstOrLast(aFrame, aContinuationStates,
+ aContainerWM.IsBidiLTR() == frameWM.IsBidiLTR(),
+ isFirst /* out */, isLast /* out */);
+
+ // We only need the margin if the frame is first or last in its own
+ // writing mode, but we're traversing the frames in the order of the
+ // container's writing mode. To get the right values, we set start and
+ // end margins on a logical margin in the frame's writing mode, and
+ // then convert the margin to the container's writing mode to set the
+ // coordinates.
+
+ // This method is called from nsBlockFrame::PlaceLine via the call to
+ // bidiUtils->ReorderFrames, so this is guaranteed to be after the inlines
+ // have been reflowed, which is required for GetUsedMargin/Border/Padding
+ nscoord frameISize = aFrame->ISize();
+ LogicalMargin frameMargin = aFrame->GetLogicalUsedMargin(frameWM);
+ LogicalMargin borderPadding = aFrame->GetLogicalUsedBorderAndPadding(frameWM);
+ // Since the visual order of frame could be different from the continuation
+ // order, we need to remove any inline border/padding [that is already applied
+ // based on continuation order] and then add it back based on the visual order
+ // (i.e. isFirst/isLast) to get the correct isize for the current frame.
+ // We don't need to do that for 'box-decoration-break:clone' because then all
+ // continuations have border/padding/margin applied.
+ if (aFrame->StyleBorder()->mBoxDecorationBreak ==
+ StyleBoxDecorationBreak::Slice) {
+ // First remove the border/padding that was applied based on logical order.
+ if (!aFrame->GetPrevContinuation()) {
+ frameISize -= borderPadding.IStart(frameWM);
+ }
+ if (!aFrame->GetNextContinuation()) {
+ frameISize -= borderPadding.IEnd(frameWM);
+ }
+ // Set margin/border/padding based on visual order.
+ if (!isFirst) {
+ frameMargin.IStart(frameWM) = 0;
+ borderPadding.IStart(frameWM) = 0;
+ }
+ if (!isLast) {
+ frameMargin.IEnd(frameWM) = 0;
+ borderPadding.IEnd(frameWM) = 0;
+ }
+ // Add the border/padding which is now based on visual order.
+ frameISize += borderPadding.IStartEnd(frameWM);
+ }
+
+ nscoord icoord = 0;
+ if (IsBidiLeaf(aFrame)) {
+ icoord +=
+ frameWM.IsOrthogonalTo(aContainerWM) ? aFrame->BSize() : frameISize;
+ } else if (RubyUtils::IsRubyBox(aFrame->Type())) {
+ icoord += RepositionRubyFrame(aFrame, aContinuationStates, aContainerWM,
+ borderPadding);
+ } else {
+ bool reverseDir = aIsEvenLevel != frameWM.IsBidiLTR();
+ icoord += reverseDir ? borderPadding.IEnd(frameWM)
+ : borderPadding.IStart(frameWM);
+ LogicalSize logicalSize(frameWM, frameISize, aFrame->BSize());
+ nsSize frameSize = logicalSize.GetPhysicalSize(frameWM);
+ // Reposition the child frames
+ for (nsIFrame* f : aFrame->PrincipalChildList()) {
+ icoord += RepositionFrame(f, aIsEvenLevel, icoord, aContinuationStates,
+ frameWM, reverseDir, frameSize);
+ }
+ icoord += reverseDir ? borderPadding.IStart(frameWM)
+ : borderPadding.IEnd(frameWM);
+ }
+
+ // In the following variables, if aContainerReverseDir is true, i.e.
+ // the container is positioning its children in reverse of its logical
+ // direction, the "StartOrEnd" refers to the distance from the frame
+ // to the inline end edge of the container, elsewise, it refers to the
+ // distance to the inline start edge.
+ const LogicalMargin margin = frameMargin.ConvertTo(aContainerWM, frameWM);
+ nscoord marginStartOrEnd = aContainerReverseDir ? margin.IEnd(aContainerWM)
+ : margin.IStart(aContainerWM);
+ nscoord frameStartOrEnd = aStartOrEnd + marginStartOrEnd;
+
+ LogicalRect rect = aFrame->GetLogicalRect(aContainerWM, aContainerSize);
+ rect.ISize(aContainerWM) = icoord;
+ rect.IStart(aContainerWM) = aContainerReverseDir
+ ? lineSize - frameStartOrEnd - icoord
+ : frameStartOrEnd;
+ aFrame->SetRect(aContainerWM, rect, aContainerSize);
+
+ return icoord + margin.IStartEnd(aContainerWM);
+}
+
+void nsBidiPresUtils::InitContinuationStates(
+ nsIFrame* aFrame, nsContinuationStates* aContinuationStates) {
+ aContinuationStates->Insert(aFrame);
+ if (!IsBidiLeaf(aFrame)) {
+ // Continue for child frames
+ for (nsIFrame* frame : aFrame->PrincipalChildList()) {
+ InitContinuationStates(frame, aContinuationStates);
+ }
+ }
+}
+
+/* static */
+nscoord nsBidiPresUtils::RepositionInlineFrames(BidiLineData* aBld,
+ WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart) {
+ nscoord start = aStart;
+ nsIFrame* frame;
+ int32_t count = aBld->mVisualFrames.Length();
+ int32_t index;
+ nsContinuationStates continuationStates;
+
+ // Initialize continuation states to (nullptr, 0) for
+ // each frame on the line.
+ for (index = 0; index < count; index++) {
+ InitContinuationStates(aBld->VisualFrameAt(index), &continuationStates);
+ }
+
+ // Reposition frames in visual order
+ int32_t step, limit;
+ if (aLineWM.IsBidiLTR()) {
+ index = 0;
+ step = 1;
+ limit = count;
+ } else {
+ index = count - 1;
+ step = -1;
+ limit = -1;
+ }
+ for (; index != limit; index += step) {
+ frame = aBld->VisualFrameAt(index);
+ start += RepositionFrame(
+ frame, !(aBld->mLevels[aBld->mIndexMap[index]].IsRTL()), start,
+ &continuationStates, aLineWM, false, aContainerSize);
+ }
+ return start;
+}
+
+bool nsBidiPresUtils::CheckLineOrder(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ nsIFrame** aFirstVisual,
+ nsIFrame** aLastVisual) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+ int32_t count = bld.FrameCount();
+
+ if (aFirstVisual) {
+ *aFirstVisual = bld.VisualFrameAt(0);
+ }
+ if (aLastVisual) {
+ *aLastVisual = bld.VisualFrameAt(count - 1);
+ }
+
+ return bld.mIsReordered;
+}
+
+nsIFrame* nsBidiPresUtils::GetFrameToRightOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count) return bld.VisualFrameAt(0);
+
+ for (int32_t i = 0; i < count - 1; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i + 1);
+ }
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsBidiPresUtils::GetFrameToLeftOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine) {
+ BidiLineData bld(aFirstFrameOnLine, aNumFramesOnLine);
+
+ int32_t count = bld.mVisualFrames.Length();
+
+ if (aFrame == nullptr && count) return bld.VisualFrameAt(count - 1);
+
+ for (int32_t i = 1; i < count; i++) {
+ if (bld.VisualFrameAt(i) == aFrame) {
+ return bld.VisualFrameAt(i - 1);
+ }
+ }
+
+ return nullptr;
+}
+
+inline void nsBidiPresUtils::EnsureBidiContinuation(
+ nsIFrame* aFrame, const nsLineList::iterator aLine, nsIFrame** aNewFrame,
+ int32_t aStart, int32_t aEnd) {
+ MOZ_ASSERT(aNewFrame, "null OUT ptr");
+ MOZ_ASSERT(aFrame, "aFrame is null");
+
+ aFrame->AdjustOffsetsForBidi(aStart, aEnd);
+ CreateContinuation(aFrame, aLine, aNewFrame, false);
+}
+
+void nsBidiPresUtils::RemoveBidiContinuation(BidiParagraphData* aBpd,
+ nsIFrame* aFrame,
+ int32_t aFirstIndex,
+ int32_t aLastIndex) {
+ FrameBidiData bidiData = aFrame->GetBidiData();
+ bidiData.precedingControl = kBidiLevelNone;
+ for (int32_t index = aFirstIndex + 1; index <= aLastIndex; index++) {
+ nsIFrame* frame = aBpd->FrameAt(index);
+ if (frame != NS_BIDI_CONTROL_FRAME) {
+ // Make the frame and its continuation ancestors fluid,
+ // so they can be reused or deleted by normal reflow code
+ frame->SetProperty(nsIFrame::BidiDataProperty(), bidiData);
+ frame->AddStateBits(NS_FRAME_IS_BIDI);
+ while (frame && IsBidiSplittable(frame)) {
+ nsIFrame* prev = frame->GetPrevContinuation();
+ if (prev) {
+ MakeContinuationFluid(prev, frame);
+ frame = frame->GetParent();
+ } else {
+ break;
+ }
+ }
+ }
+ }
+
+ // Make sure that the last continuation we made fluid does not itself have a
+ // fluid continuation (this can happen when re-resolving after dynamic changes
+ // to content)
+ nsIFrame* lastFrame = aBpd->FrameAt(aLastIndex);
+ MakeContinuationsNonFluidUpParentChain(lastFrame, lastFrame->GetNextInFlow());
+}
+
+nsresult nsBidiPresUtils::FormatUnicodeText(nsPresContext* aPresContext,
+ char16_t* aText,
+ int32_t& aTextLength,
+ BidiClass aBidiClass) {
+ nsresult rv = NS_OK;
+ // ahmed
+ // adjusted for correct numeral shaping
+ uint32_t bidiOptions = aPresContext->GetBidi();
+ switch (GET_BIDI_OPTION_NUMERAL(bidiOptions)) {
+ case IBMBIDI_NUMERAL_HINDI:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ case IBMBIDI_NUMERAL_ARABIC:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIAN:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
+ break;
+
+ case IBMBIDI_NUMERAL_REGULAR:
+
+ switch (aBidiClass) {
+ case BidiClass::EuropeanNumber:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ break;
+
+ case BidiClass::ArabicNumber:
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ break;
+
+ default:
+ break;
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_HINDICONTEXT:
+ if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
+ IBMBIDI_TEXTDIRECTION_RTL) &&
+ (IS_ARABIC_DIGIT(aText[0]))) ||
+ (BidiClass::ArabicNumber == aBidiClass)) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_HINDI);
+ } else if (BidiClass::EuropeanNumber == aBidiClass) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_PERSIANCONTEXT:
+ if (((GET_BIDI_OPTION_DIRECTION(bidiOptions) ==
+ IBMBIDI_TEXTDIRECTION_RTL) &&
+ (IS_ARABIC_DIGIT(aText[0]))) ||
+ (BidiClass::ArabicNumber == aBidiClass)) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_PERSIAN);
+ } else if (BidiClass::EuropeanNumber == aBidiClass) {
+ HandleNumbers(aText, aTextLength, IBMBIDI_NUMERAL_ARABIC);
+ }
+ break;
+
+ case IBMBIDI_NUMERAL_NOMINAL:
+ default:
+ break;
+ }
+
+ StripBidiControlCharacters(aText, aTextLength);
+ return rv;
+}
+
+void nsBidiPresUtils::StripBidiControlCharacters(char16_t* aText,
+ int32_t& aTextLength) {
+ if ((nullptr == aText) || (aTextLength < 1)) {
+ return;
+ }
+
+ int32_t stripLen = 0;
+
+ for (int32_t i = 0; i < aTextLength; i++) {
+ // XXX: This silently ignores surrogate characters.
+ // As of Unicode 4.0, all Bidi control characters are within the BMP.
+ if (IsBidiControl((uint32_t)aText[i])) {
+ ++stripLen;
+ } else {
+ aText[i - stripLen] = aText[i];
+ }
+ }
+ aTextLength -= stripLen;
+}
+
+void nsBidiPresUtils::CalculateBidiClass(
+ const char16_t* aText, int32_t& aOffset, int32_t aBidiClassLimit,
+ int32_t& aRunLimit, int32_t& aRunLength, int32_t& aRunCount,
+ BidiClass& aBidiClass, BidiClass& aPrevBidiClass) {
+ bool strongTypeFound = false;
+ int32_t offset;
+ BidiClass bidiClass;
+
+ aBidiClass = BidiClass::OtherNeutral;
+
+ int32_t charLen;
+ for (offset = aOffset; offset < aBidiClassLimit; offset += charLen) {
+ // Make sure we give RTL chartype to all characters that would be classified
+ // as Right-To-Left by a bidi platform.
+ // (May differ from the UnicodeData, eg we set RTL chartype to some NSMs.)
+ charLen = 1;
+ uint32_t ch = aText[offset];
+ if (IS_HEBREW_CHAR(ch)) {
+ bidiClass = BidiClass::RightToLeft;
+ } else if (IS_ARABIC_ALPHABETIC(ch)) {
+ bidiClass = BidiClass::RightToLeftArabic;
+ } else {
+ if (offset + 1 < aBidiClassLimit &&
+ NS_IS_SURROGATE_PAIR(ch, aText[offset + 1])) {
+ ch = SURROGATE_TO_UCS4(ch, aText[offset + 1]);
+ charLen = 2;
+ }
+ bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
+ }
+
+ if (!BIDICLASS_IS_WEAK(bidiClass)) {
+ if (strongTypeFound && (bidiClass != aPrevBidiClass) &&
+ (BIDICLASS_IS_RTL(bidiClass) || BIDICLASS_IS_RTL(aPrevBidiClass))) {
+ // Stop at this point to ensure uni-directionality of the text
+ // (from platform's point of view).
+ // Also, don't mix Arabic and Hebrew content (since platform may
+ // provide BIDI support to one of them only).
+ aRunLength = offset - aOffset;
+ aRunLimit = offset;
+ ++aRunCount;
+ break;
+ }
+
+ if ((BidiClass::RightToLeftArabic == aPrevBidiClass ||
+ BidiClass::ArabicNumber == aPrevBidiClass) &&
+ BidiClass::EuropeanNumber == bidiClass) {
+ bidiClass = BidiClass::ArabicNumber;
+ }
+
+ // Set PrevBidiClass to the last strong type in this frame
+ // (for correct numeric shaping)
+ aPrevBidiClass = bidiClass;
+
+ strongTypeFound = true;
+ aBidiClass = bidiClass;
+ }
+ }
+ aOffset = offset;
+}
+
+nsresult nsBidiPresUtils::ProcessText(const char16_t* aText, size_t aLength,
+ BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth,
+ BidiEngine& aBidiEngine) {
+ MOZ_ASSERT((aPosResolve == nullptr) != (aPosResolveCount > 0),
+ "Incorrect aPosResolve / aPosResolveCount arguments");
+
+ // Caller should have already replaced any separators in the original text
+ // with <space> characters.
+ MOZ_ASSERT(nsDependentSubstring(aText, aLength).FindCharInSet(kSeparators) ==
+ kNotFound);
+
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ aPosResolve[nPosResolve].visualIndex = kNotFound;
+ aPosResolve[nPosResolve].visualLeftTwips = kNotFound;
+ aPosResolve[nPosResolve].visualWidth = kNotFound;
+ }
+
+ // For a single-char string, or a string that is purely LTR, use a simplified
+ // path as it cannot have multiple direction or bidi-class runs.
+ if (aLength == 1 ||
+ (aLength == 2 && NS_IS_SURROGATE_PAIR(aText[0], aText[1])) ||
+ (aBaseLevel.Direction() == BidiDirection::LTR &&
+ !encoding_mem_is_utf16_bidi(aText, aLength))) {
+ ProcessSimpleRun(aText, aLength, aBaseLevel, aPresContext, aprocessor,
+ aMode, aPosResolve, aPosResolveCount, aWidth);
+ return NS_OK;
+ }
+
+ if (aBidiEngine.SetParagraph(Span(aText, aLength), aBaseLevel).isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto result = aBidiEngine.CountRuns();
+ if (result.isErr()) {
+ return NS_ERROR_FAILURE;
+ }
+ int32_t runCount = result.unwrap();
+
+ nscoord xOffset = 0;
+ nscoord width, xEndRun = 0;
+ nscoord totalWidth = 0;
+ int32_t i, start, limit, length;
+ uint32_t visualStart = 0;
+ BidiClass bidiClass;
+ BidiClass prevClass = BidiClass::LeftToRight;
+
+ for (i = 0; i < runCount; i++) {
+ aBidiEngine.GetVisualRun(i, &start, &length);
+
+ BidiEmbeddingLevel level;
+ aBidiEngine.GetLogicalRun(start, &limit, &level);
+
+ BidiDirection dir = level.Direction();
+ int32_t subRunLength = limit - start;
+ int32_t lineOffset = start;
+ int32_t typeLimit = std::min(limit, AssertedCast<int32_t>(aLength));
+ int32_t subRunCount = 1;
+ int32_t subRunLimit = typeLimit;
+
+ /*
+ * If |level| is even, i.e. the direction of the run is left-to-right, we
+ * render the subruns from left to right and increment the x-coordinate
+ * |xOffset| by the width of each subrun after rendering.
+ *
+ * If |level| is odd, i.e. the direction of the run is right-to-left, we
+ * render the subruns from right to left. We begin by incrementing |xOffset|
+ * by the width of the whole run, and then decrement it by the width of each
+ * subrun before rendering. After rendering all the subruns, we restore the
+ * x-coordinate of the end of the run for the start of the next run.
+ */
+
+ if (dir == BidiDirection::RTL) {
+ aprocessor.SetText(aText + start, subRunLength, BidiDirection::RTL);
+ width = aprocessor.GetWidth();
+ xOffset += width;
+ xEndRun = xOffset;
+ }
+
+ while (subRunCount > 0) {
+ // CalculateBidiClass can increment subRunCount if the run
+ // contains mixed character types
+ CalculateBidiClass(aText, lineOffset, typeLimit, subRunLimit,
+ subRunLength, subRunCount, bidiClass, prevClass);
+
+ nsAutoString runVisualText(aText + start, subRunLength);
+ if (aPresContext) {
+ FormatUnicodeText(aPresContext, runVisualText.BeginWriting(),
+ subRunLength, bidiClass);
+ }
+
+ aprocessor.SetText(runVisualText.get(), subRunLength, dir);
+ width = aprocessor.GetWidth();
+ totalWidth += width;
+ if (dir == BidiDirection::RTL) {
+ xOffset -= width;
+ }
+ if (aMode == MODE_DRAW) {
+ aprocessor.DrawText(xOffset);
+ }
+
+ /*
+ * The caller may request to calculate the visual position of one
+ * or more characters.
+ */
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
+ /*
+ * Did we already resolve this position's visual metric? If so, skip.
+ */
+ if (posResolve->visualLeftTwips != kNotFound) continue;
+
+ /*
+ * First find out if the logical position is within this run.
+ */
+ if (start <= posResolve->logicalIndex &&
+ start + subRunLength > posResolve->logicalIndex) {
+ /*
+ * If this run is only one character long, we have an easy case:
+ * the visual position is the x-coord of the start of the run
+ * less the x-coord of the start of the whole text.
+ */
+ if (subRunLength == 1) {
+ posResolve->visualIndex = visualStart;
+ posResolve->visualLeftTwips = xOffset;
+ posResolve->visualWidth = width;
+ }
+ /*
+ * Otherwise, we need to measure the width of the run's part
+ * which is to the visual left of the index.
+ * In other words, the run is broken in two, around the logical index,
+ * and we measure the part which is visually left.
+ * If the run is right-to-left, this part will span from after the
+ * index up to the end of the run; if it is left-to-right, this part
+ * will span from the start of the run up to (and inclduing) the
+ * character before the index.
+ */
+ else {
+ /*
+ * Here is a description of how the width of the current character
+ * (posResolve->visualWidth) is calculated:
+ *
+ * LTR (current char: "P"):
+ * S A M P L E (logical index: 3, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ *
+ * RTL (current char: "M"):
+ * E L P M A S (logical index: 2, visual index: 3)
+ * ^ (visualLeftPart)
+ * ^ (visualRightSide)
+ * visualLeftLength == 3
+ * ^^^^^^ (subWidth)
+ * ^^^^^^^^ (aprocessor.GetWidth() -- with visualRightSide)
+ * ^^ (posResolve->visualWidth)
+ */
+ nscoord subWidth;
+ // The position in the text where this run's "left part" begins.
+ const char16_t* visualLeftPart;
+ const char16_t* visualRightSide;
+ if (dir == BidiDirection::RTL) {
+ // One day, son, this could all be replaced with
+ // mPresContext->BidiEngine().GetVisualIndex() ...
+ posResolve->visualIndex =
+ visualStart +
+ (subRunLength - (posResolve->logicalIndex + 1 - start));
+ // Skipping to the "left part".
+ visualLeftPart = aText + posResolve->logicalIndex + 1;
+ // Skipping to the right side of the current character
+ visualRightSide = visualLeftPart - 1;
+ } else {
+ posResolve->visualIndex =
+ visualStart + (posResolve->logicalIndex - start);
+ // Skipping to the "left part".
+ visualLeftPart = aText + start;
+ // In LTR mode this is the same as visualLeftPart
+ visualRightSide = visualLeftPart;
+ }
+ // The delta between the start of the run and the left part's end.
+ int32_t visualLeftLength = posResolve->visualIndex - visualStart;
+ aprocessor.SetText(visualLeftPart, visualLeftLength, dir);
+ subWidth = aprocessor.GetWidth();
+ aprocessor.SetText(visualRightSide, visualLeftLength + 1, dir);
+ posResolve->visualLeftTwips = xOffset + subWidth;
+ posResolve->visualWidth = aprocessor.GetWidth() - subWidth;
+ }
+ }
+ }
+
+ if (dir == BidiDirection::LTR) {
+ xOffset += width;
+ }
+
+ --subRunCount;
+ start = lineOffset;
+ subRunLimit = typeLimit;
+ subRunLength = typeLimit - lineOffset;
+ } // while
+ if (dir == BidiDirection::RTL) {
+ xOffset = xEndRun;
+ }
+
+ visualStart += length;
+ } // for
+
+ if (aWidth) {
+ *aWidth = totalWidth;
+ }
+ return NS_OK;
+}
+
+// This is called either for a single character (one code unit, or a surrogate
+// pair), or for a run that is known to be purely LTR.
+void nsBidiPresUtils::ProcessSimpleRun(const char16_t* aText, size_t aLength,
+ BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount,
+ nscoord* aWidth) {
+ if (!aLength) {
+ if (aWidth) {
+ *aWidth = 0;
+ }
+ return;
+ }
+ // Get bidi class from the first (or only) character.
+ uint32_t ch = aText[0];
+ if (aLength > 1 && NS_IS_HIGH_SURROGATE(ch) &&
+ NS_IS_LOW_SURROGATE(aText[1])) {
+ ch = SURROGATE_TO_UCS4(aText[0], aText[1]);
+ }
+ BidiClass bidiClass = intl::UnicodeProperties::GetBidiClass(ch);
+
+ nsAutoString runVisualText(aText, aLength);
+ int32_t length = aLength;
+ if (aPresContext) {
+ FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), length,
+ bidiClass);
+ }
+
+ BidiDirection dir = bidiClass == BidiClass::RightToLeft ||
+ bidiClass == BidiClass::RightToLeftArabic
+ ? BidiDirection::RTL
+ : BidiDirection::LTR;
+ aprocessor.SetText(runVisualText.get(), length, dir);
+
+ if (aMode == MODE_DRAW) {
+ aprocessor.DrawText(0);
+ }
+
+ if (!aWidth && !aPosResolve) {
+ return;
+ }
+
+ nscoord width = aprocessor.GetWidth();
+
+ for (int nPosResolve = 0; nPosResolve < aPosResolveCount; ++nPosResolve) {
+ nsBidiPositionResolve* posResolve = &aPosResolve[nPosResolve];
+ if (posResolve->visualLeftTwips != kNotFound) {
+ continue;
+ }
+ if (0 <= posResolve->logicalIndex && length > posResolve->logicalIndex) {
+ posResolve->visualIndex = 0;
+ posResolve->visualLeftTwips = 0;
+ posResolve->visualWidth = width;
+ }
+ }
+
+ if (aWidth) {
+ *aWidth = width;
+ }
+}
+
+class MOZ_STACK_CLASS nsIRenderingContextBidiProcessor final
+ : public nsBidiPresUtils::BidiProcessor {
+ public:
+ typedef gfx::DrawTarget DrawTarget;
+
+ nsIRenderingContextBidiProcessor(gfxContext* aCtx,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics* aFontMetrics,
+ const nsPoint& aPt)
+ : mCtx(aCtx),
+ mTextRunConstructionDrawTarget(aTextRunConstructionDrawTarget),
+ mFontMetrics(aFontMetrics),
+ mPt(aPt),
+ mText(nullptr),
+ mLength(0) {}
+
+ ~nsIRenderingContextBidiProcessor() { mFontMetrics->SetTextRunRTL(false); }
+
+ virtual void SetText(const char16_t* aText, int32_t aLength,
+ BidiDirection aDirection) override {
+ mFontMetrics->SetTextRunRTL(aDirection == BidiDirection::RTL);
+ mText = aText;
+ mLength = aLength;
+ }
+
+ virtual nscoord GetWidth() override {
+ return nsLayoutUtils::AppUnitWidthOfString(mText, mLength, *mFontMetrics,
+ mTextRunConstructionDrawTarget);
+ }
+
+ virtual void DrawText(nscoord aIOffset) override {
+ nsPoint pt(mPt);
+ if (mFontMetrics->GetVertical()) {
+ pt.y += aIOffset;
+ } else {
+ pt.x += aIOffset;
+ }
+ mFontMetrics->DrawString(mText, mLength, pt.x, pt.y, mCtx,
+ mTextRunConstructionDrawTarget);
+ }
+
+ private:
+ gfxContext* mCtx;
+ DrawTarget* mTextRunConstructionDrawTarget;
+ nsFontMetrics* mFontMetrics;
+ nsPoint mPt;
+ const char16_t* mText;
+ int32_t mLength;
+};
+
+nsresult nsBidiPresUtils::ProcessTextForRenderingContext(
+ const char16_t* aText, int32_t aLength, BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext, gfxContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget, nsFontMetrics& aFontMetrics,
+ Mode aMode, nscoord aX, nscoord aY, nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth) {
+ nsIRenderingContextBidiProcessor processor(&aRenderingContext,
+ aTextRunConstructionDrawTarget,
+ &aFontMetrics, nsPoint(aX, aY));
+ nsAutoString text(aText, aLength);
+ text.ReplaceChar(kSeparators, ' ');
+ return ProcessText(text.BeginReading(), text.Length(), aBaseLevel,
+ aPresContext, processor, aMode, aPosResolve,
+ aPosResolveCount, aWidth, aPresContext->BidiEngine());
+}
+
+/* static */
+BidiEmbeddingLevel nsBidiPresUtils::BidiLevelFromStyle(
+ ComputedStyle* aComputedStyle) {
+ if (aComputedStyle->StyleTextReset()->mUnicodeBidi ==
+ StyleUnicodeBidi::Plaintext) {
+ return BidiEmbeddingLevel::DefaultLTR();
+ }
+
+ if (aComputedStyle->StyleVisibility()->mDirection == StyleDirection::Rtl) {
+ return BidiEmbeddingLevel::RTL();
+ }
+
+ return BidiEmbeddingLevel::LTR();
+}
diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h
new file mode 100644
index 0000000000..c369a06f2d
--- /dev/null
+++ b/layout/base/nsBidiPresUtils.h
@@ -0,0 +1,587 @@
+/* -*- 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 nsBidiPresUtils_h___
+#define nsBidiPresUtils_h___
+
+#include "gfxContext.h"
+#include "mozilla/intl/BidiClass.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "nsBidiUtils.h"
+#include "nsHashKeys.h"
+#include "nsCoord.h"
+#include "nsTArray.h"
+#include "nsLineBox.h"
+
+#ifdef DrawText
+# undef DrawText
+#endif
+
+struct BidiParagraphData;
+struct BidiLineData;
+class gfxContext;
+class nsFontMetrics;
+class nsIFrame;
+class nsBlockFrame;
+class nsPresContext;
+struct nsSize;
+template <class T>
+class nsTHashtable;
+namespace mozilla {
+namespace intl {
+class Bidi;
+}
+class ComputedStyle;
+class LogicalMargin;
+class WritingMode;
+} // namespace mozilla
+
+/**
+ * A structure representing some continuation state for each frame on the line,
+ * used to determine the first and the last continuation frame for each
+ * continuation chain on the line.
+ */
+struct nsFrameContinuationState : public nsVoidPtrHashKey {
+ explicit nsFrameContinuationState(const void* aFrame)
+ : nsVoidPtrHashKey(aFrame) {}
+
+ /**
+ * The first visual frame in the continuation chain containing this frame, or
+ * nullptr if this frame is the first visual frame in the chain.
+ */
+ nsIFrame* mFirstVisualFrame{nullptr};
+
+ /**
+ * The number of frames in the continuation chain containing this frame, if
+ * this frame is the first visual frame of the chain, or 0 otherwise.
+ */
+ uint32_t mFrameCount{0};
+
+ /**
+ * TRUE if this frame is the first visual frame of its continuation chain on
+ * this line and the chain has some frames on the previous lines.
+ */
+ bool mHasContOnPrevLines{false};
+
+ /**
+ * TRUE if this frame is the first visual frame of its continuation chain on
+ * this line and the chain has some frames left for next lines.
+ */
+ bool mHasContOnNextLines{false};
+};
+
+// A table of nsFrameContinuationState objects.
+//
+// This state is used between calls to nsBidiPresUtils::IsFirstOrLast.
+struct nsContinuationStates {
+ static constexpr size_t kArrayMax = 32;
+
+ // We use the array to gather up all the continuation state objects. If in
+ // the end there are more than kArrayMax of them, we convert it to a hash
+ // table for faster lookup.
+ bool mUseTable = false;
+ AutoTArray<nsFrameContinuationState, kArrayMax> mValues;
+ nsTHashtable<nsFrameContinuationState> mTable;
+
+ void Insert(nsIFrame* aFrame) {
+ if (MOZ_UNLIKELY(mUseTable)) {
+ mTable.PutEntry(aFrame);
+ return;
+ }
+ if (MOZ_LIKELY(mValues.Length() < kArrayMax)) {
+ mValues.AppendElement(aFrame);
+ return;
+ }
+ for (const auto& entry : mValues) {
+ mTable.PutEntry(entry.GetKey());
+ }
+ mTable.PutEntry(aFrame);
+ mValues.Clear();
+ mUseTable = true;
+ }
+
+ nsFrameContinuationState* Get(nsIFrame* aFrame) {
+ MOZ_ASSERT(mValues.IsEmpty() != mTable.IsEmpty(),
+ "expect entries to either be in mValues or in mTable");
+ if (mUseTable) {
+ return mTable.GetEntry(aFrame);
+ }
+ for (size_t i = 0, len = mValues.Length(); i != len; ++i) {
+ if (mValues[i].GetKey() == aFrame) {
+ return &mValues[i];
+ }
+ }
+ return nullptr;
+ }
+};
+
+/**
+ * A structure representing a logical position which should be resolved
+ * into its visual position during BiDi processing.
+ */
+struct nsBidiPositionResolve {
+ // [in] Logical index within string.
+ int32_t logicalIndex;
+ // [out] Visual index within string.
+ // If the logical position was not found, set to kNotFound.
+ int32_t visualIndex;
+ // [out] Visual position of the character, from the left (on the X axis), in
+ // twips. Eessentially, this is the X position (relative to the rendering
+ // context) where the text was drawn + the font metric of the visual string to
+ // the left of the given logical position. If the logical position was not
+ // found, set to kNotFound.
+ int32_t visualLeftTwips;
+ // [out] Visual width of the character, in twips.
+ // If the logical position was not found, set to kNotFound.
+ int32_t visualWidth;
+};
+
+class nsBidiPresUtils {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ nsBidiPresUtils();
+ ~nsBidiPresUtils();
+
+ /**
+ * Interface for the processor used by ProcessText. Used by process text to
+ * collect information about the width of subruns and to notify where each
+ * subrun should be rendered.
+ */
+ class BidiProcessor {
+ public:
+ virtual ~BidiProcessor() = default;
+
+ /**
+ * Sets the current text with the given length and the given direction.
+ *
+ * @remark The reason that the function gives a string instead of an index
+ * is that ProcessText copies and modifies the string passed to it, so
+ * passing an index would be impossible.
+ *
+ * @param aText The string of text.
+ * @param aLength The length of the string of text.
+ * @param aDirection The direction of the text. The string will never have
+ * mixed direction.
+ */
+ virtual void SetText(const char16_t* aText, int32_t aLength,
+ mozilla::intl::BidiDirection aDirection) = 0;
+
+ /**
+ * Returns the measured width of the text given in SetText. If SetText was
+ * not called with valid parameters, the result of this call is undefined.
+ * This call is guaranteed to only be called once between SetText calls.
+ * Will be invoked before DrawText.
+ */
+ virtual nscoord GetWidth() = 0;
+
+ /**
+ * Draws the text given in SetText to a rendering context. If SetText was
+ * not called with valid parameters, the result of this call is undefined.
+ * This call is guaranteed to only be called once between SetText calls.
+ *
+ * @param aXOffset The offset of the left side of the substring to be drawn
+ * from the beginning of the overall string passed to ProcessText.
+ */
+ virtual void DrawText(nscoord aXOffset) = 0;
+ };
+
+ /**
+ * Make Bidi engine calculate the embedding levels of the frames that are
+ * descendants of a given block frame.
+ *
+ * @param aBlockFrame The block frame
+ *
+ * @lina 06/18/2000
+ */
+ static nsresult Resolve(nsBlockFrame* aBlockFrame);
+ static nsresult ResolveParagraph(BidiParagraphData* aBpd);
+ static void ResolveParagraphWithinBlock(BidiParagraphData* aBpd);
+
+ /**
+ * Reorder this line using Bidi engine.
+ * Update frame array, following the new visual sequence.
+ *
+ * @return total inline size
+ *
+ * @lina 05/02/2000
+ */
+ static nscoord ReorderFrames(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine,
+ mozilla::WritingMode aLineWM,
+ const nsSize& aContainerSize, nscoord aStart);
+
+ /**
+ * Format Unicode text, taking into account bidi capabilities
+ * of the platform. The formatting includes: reordering, Arabic shaping,
+ * symmetric and numeric swapping, removing control characters.
+ *
+ * @lina 06/18/2000
+ */
+ static nsresult FormatUnicodeText(nsPresContext* aPresContext,
+ char16_t* aText, int32_t& aTextLength,
+ mozilla::intl::BidiClass aBidiClass);
+
+ /**
+ * Reorder plain text using the Unicode Bidi algorithm and send it to
+ * a rendering context for rendering.
+ *
+ * @param[in] aText the string to be rendered (in logical order)
+ * @param aLength the number of characters in the string
+ * @param aBaseLevel the base embedding level of the string
+ * @param aPresContext the presentation context
+ * @param aRenderingContext the rendering context to render to
+ * @param aTextRunConstructionContext the rendering context to be used to
+ * construct the textrun (affects font hinting)
+ * @param aX the x-coordinate to render the string
+ * @param aY the y-coordinate to render the string
+ * @param[in,out] aPosResolve array of logical positions to resolve into
+ * visual positions; can be nullptr if this functionality is not required
+ * @param aPosResolveCount number of items in the aPosResolve array
+ */
+ static nsresult RenderText(const char16_t* aText, int32_t aLength,
+ mozilla::intl::BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics& aFontMetrics, nscoord aX,
+ nscoord aY,
+ nsBidiPositionResolve* aPosResolve = nullptr,
+ int32_t aPosResolveCount = 0) {
+ return ProcessTextForRenderingContext(
+ aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
+ aTextRunConstructionDrawTarget, aFontMetrics, MODE_DRAW, aX, aY,
+ aPosResolve, aPosResolveCount, nullptr);
+ }
+
+ static nscoord MeasureTextWidth(const char16_t* aText, int32_t aLength,
+ mozilla::intl::BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ gfxContext& aRenderingContext,
+ nsFontMetrics& aFontMetrics) {
+ nscoord length;
+ nsresult rv = ProcessTextForRenderingContext(
+ aText, aLength, aBaseLevel, aPresContext, aRenderingContext,
+ aRenderingContext.GetDrawTarget(), aFontMetrics, MODE_MEASURE, 0, 0,
+ nullptr, 0, &length);
+ return NS_SUCCEEDED(rv) ? length : 0;
+ }
+
+ /**
+ * Check if a line is reordered, i.e., if the child frames are not
+ * all laid out left-to-right.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ * @param[out] aLeftMost : leftmost frame on this line
+ * @param[out] aRightMost : rightmost frame on this line
+ */
+ static bool CheckLineOrder(nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine, nsIFrame** aLeftmost,
+ nsIFrame** aRightmost);
+
+ /**
+ * Get the frame to the right of the given frame, on the same line.
+ * @param aFrame : We're looking for the frame to the right of this frame.
+ * If null, return the leftmost frame on the line.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ */
+ static nsIFrame* GetFrameToRightOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine);
+
+ /**
+ * Get the frame to the left of the given frame, on the same line.
+ * @param aFrame : We're looking for the frame to the left of this frame.
+ * If null, return the rightmost frame on the line.
+ * @param aFirstFrameOnLine : first frame of the line to be tested
+ * @param aNumFramesOnLine : number of frames on this line
+ */
+ static nsIFrame* GetFrameToLeftOf(const nsIFrame* aFrame,
+ nsIFrame* aFirstFrameOnLine,
+ int32_t aNumFramesOnLine);
+
+ static nsIFrame* GetFirstLeaf(nsIFrame* aFrame);
+
+ /**
+ * Get the bidi data of the given (inline) frame.
+ */
+ static mozilla::FrameBidiData GetFrameBidiData(nsIFrame* aFrame);
+
+ /**
+ * Get the bidi embedding level of the given (inline) frame.
+ */
+ static mozilla::intl::BidiEmbeddingLevel GetFrameEmbeddingLevel(
+ nsIFrame* aFrame);
+
+ /**
+ * Get the bidi base level of the given (inline) frame.
+ */
+ static mozilla::intl::BidiEmbeddingLevel GetFrameBaseLevel(
+ const nsIFrame* aFrame);
+
+ /**
+ * Get a mozilla::intl::BidiDirection representing the direction implied by
+ * the bidi base level of the frame.
+ * @return mozilla::intl::BidiDirection
+ */
+ static mozilla::intl::BidiDirection ParagraphDirection(
+ const nsIFrame* aFrame) {
+ return GetFrameBaseLevel(aFrame).Direction();
+ }
+
+ /**
+ * Get a mozilla::intl::BidiDirection representing the direction implied by
+ * the bidi embedding level of the frame.
+ * @return mozilla::intl::BidiDirection
+ */
+ static mozilla::intl::BidiDirection FrameDirection(nsIFrame* aFrame) {
+ return GetFrameEmbeddingLevel(aFrame).Direction();
+ }
+
+ static bool IsFrameInParagraphDirection(nsIFrame* aFrame) {
+ return ParagraphDirection(aFrame) == FrameDirection(aFrame);
+ }
+
+ // This is faster than nsBidiPresUtils::IsFrameInParagraphDirection,
+ // because it uses the frame pointer passed in without drilling down to
+ // the leaf frame.
+ static bool IsReversedDirectionFrame(const nsIFrame* aFrame) {
+ mozilla::FrameBidiData bidiData = aFrame->GetBidiData();
+ return !bidiData.embeddingLevel.IsSameDirection(bidiData.baseLevel);
+ }
+
+ enum Mode { MODE_DRAW, MODE_MEASURE };
+
+ /**
+ * Reorder plain text using the Unicode Bidi algorithm and send it to
+ * a processor for rendering or measuring
+ *
+ * @param[in] aText the string to be processed (in logical order)
+ * @param aLength the number of characters in the string
+ * @param aBaseLevel the base embedding level of the string
+ * @param aPresContext the presentation context
+ * @param aprocessor the bidi processor
+ * @param aMode the operation to process
+ * MODE_DRAW - invokes DrawText on the processor for each substring
+ * MODE_MEASURE - does not invoke DrawText on the processor
+ * Note that the string is always measured, regardless of mode
+ * @param[in,out] aPosResolve array of logical positions to resolve into
+ * visual positions; can be nullptr if this functionality is not required
+ * @param aPosResolveCount number of items in the aPosResolve array
+ * @param[out] aWidth Pointer to where the width will be stored (may be null)
+ */
+ static nsresult ProcessText(const char16_t* aText, size_t aLength,
+ mozilla::intl::BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth,
+ mozilla::intl::Bidi& aBidiEngine);
+
+ /**
+ * Use style attributes to determine the base paragraph level to pass to the
+ * bidi algorithm.
+ *
+ * If |unicode-bidi| is set to "[-moz-]plaintext", returns
+ * BidiEmbeddingLevel::DefaultLTR, in other words the direction is determined
+ * from the first strong character in the text according to rules P2 and P3 of
+ * the bidi algorithm, or LTR if there is no strong character.
+ *
+ * Otherwise returns BidiEmbeddingLevel::LTR or BidiEmbeddingLevel::RTL
+ * depending on the value of |direction|
+ */
+ static mozilla::intl::BidiEmbeddingLevel BidiLevelFromStyle(
+ mozilla::ComputedStyle* aComputedStyle);
+
+ private:
+ static nsresult ProcessTextForRenderingContext(
+ const char16_t* aText, int32_t aLength,
+ mozilla::intl::BidiEmbeddingLevel aBaseLevel, nsPresContext* aPresContext,
+ gfxContext& aRenderingContext, DrawTarget* aTextRunConstructionDrawTarget,
+ nsFontMetrics& aFontMetrics, Mode aMode,
+ nscoord aX, // DRAW only
+ nscoord aY, // DRAW only
+ nsBidiPositionResolve* aPosResolve, /* may be null */
+ int32_t aPosResolveCount, nscoord* aWidth /* may be null */);
+
+ /**
+ * Simplified form of ProcessText body, used when aText is a single Unicode
+ * character (one UTF-16 codepoint, or a surrogate pair), or a run that is
+ * known to have no bidi content.
+ */
+ static void ProcessSimpleRun(const char16_t* aText, size_t aLength,
+ mozilla::intl::BidiEmbeddingLevel aBaseLevel,
+ nsPresContext* aPresContext,
+ BidiProcessor& aprocessor, Mode aMode,
+ nsBidiPositionResolve* aPosResolve,
+ int32_t aPosResolveCount, nscoord* aWidth);
+
+ /**
+ * Traverse the child frames of the block element and:
+ * Set up an array of the frames in logical order
+ * Create a string containing the text content of all the frames
+ * If we encounter content that requires us to split the element into more
+ * than one paragraph for bidi resolution, resolve the paragraph up to that
+ * point.
+ */
+ static void TraverseFrames(nsIFrame* aCurrentFrame, BidiParagraphData* aBpd);
+
+ /**
+ * Perform a recursive "pre-traversal" of the child frames of a block or
+ * inline container frame, to determine whether full bidi resolution is
+ * actually needed.
+ * This explores the same frames as TraverseFrames (above), but is less
+ * expensive and may allow us to avoid performing the full TraverseFrames
+ * operation.
+ * @param aFirstChild frame to start traversal from
+ * @param[in/out] aCurrContent the content node that we've most recently
+ * scanned for RTL characters (so that when descendant frames refer
+ * to the same content, we can avoid repeatedly scanning it).
+ * @return true if it finds that bidi is (or may be) required,
+ * false if no potentially-bidi content is present.
+ */
+ static bool ChildListMayRequireBidi(nsIFrame* aFirstChild,
+ nsIContent** aCurrContent);
+
+ /**
+ * Position ruby content frames (ruby base/text frame).
+ * Called from RepositionRubyFrame.
+ */
+ static void RepositionRubyContentFrame(
+ nsIFrame* aFrame, mozilla::WritingMode aFrameWM,
+ const mozilla::LogicalMargin& aBorderPadding);
+
+ /*
+ * Position ruby frames. Called from RepositionFrame.
+ */
+ static nscoord RepositionRubyFrame(
+ nsIFrame* aFrame, nsContinuationStates* aContinuationStates,
+ const mozilla::WritingMode aContainerWM,
+ const mozilla::LogicalMargin& aBorderPadding);
+
+ /*
+ * Position aFrame and its descendants to their visual places. Also if aFrame
+ * is not leaf, resize it to embrace its children.
+ *
+ * @param aFrame The frame which itself and its children are
+ * going to be repositioned
+ * @param aIsEvenLevel TRUE means the embedding level of this frame
+ * is even (LTR)
+ * @param aStartOrEnd The distance to the start or the end of aFrame
+ * without considering its inline margin. If the
+ * container is reordering frames in reverse
+ * direction, it's the distance to the end,
+ * otherwise, it's the distance to the start.
+ * @param aContinuationStates A map from nsIFrame* to
+ * nsFrameContinuationState
+ * @return The isize aFrame takes, including margins.
+ */
+ static nscoord RepositionFrame(nsIFrame* aFrame, bool aIsEvenLevel,
+ nscoord aStartOrEnd,
+ nsContinuationStates* aContinuationStates,
+ mozilla::WritingMode aContainerWM,
+ bool aContainerReverseOrder,
+ const nsSize& aContainerSize);
+
+ /*
+ * Initialize the continuation state(nsFrameContinuationState) to
+ * (nullptr, 0) for aFrame and its descendants.
+ *
+ * @param aFrame The frame which itself and its descendants will
+ * be initialized
+ * @param aContinuationStates A map from nsIFrame* to
+ * nsFrameContinuationState
+ */
+ static void InitContinuationStates(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates);
+
+ /*
+ * Determine if aFrame is first or last, and set aIsFirst and
+ * aIsLast values. Also set continuation states of
+ * aContinuationStates.
+ *
+ * A frame is first if it's the first appearance of its continuation
+ * chain on the line and the chain is on its first line.
+ * A frame is last if it's the last appearance of its continuation
+ * chain on the line and the chain is on its last line.
+ *
+ * N.B: "First appearance" and "Last appearance" in the previous
+ * paragraph refer to the frame's inline direction, not necessarily
+ * the line's.
+ *
+ * @param aContinuationStates A map from nsIFrame* to
+ * nsFrameContinuationState
+ * @param[in] aSpanDirMatchesLineDir TRUE means that the inline
+ * direction of aFrame is the same
+ * as its container
+ * @param[out] aIsFirst TRUE means aFrame is first frame
+ * or continuation
+ * @param[out] aIsLast TRUE means aFrame is last frame
+ * or continuation
+ */
+ static void IsFirstOrLast(nsIFrame* aFrame,
+ nsContinuationStates* aContinuationStates,
+ bool aSpanInLineOrder /* in */,
+ bool& aIsFirst /* out */, bool& aIsLast /* out */);
+
+ /**
+ * Adjust frame positions following their visual order
+ *
+ * @param aFirstChild the first kid
+ * @return total inline size
+ *
+ * @lina 04/11/2000
+ */
+ static nscoord RepositionInlineFrames(BidiLineData* aBld,
+ mozilla::WritingMode aLineWM,
+ const nsSize& aContainerSize,
+ nscoord aStart);
+
+ /**
+ * Helper method for Resolve()
+ * Truncate a text frame to the end of a single-directional run and possibly
+ * create a continuation frame for the remainder of its content.
+ *
+ * @param aFrame the original frame
+ * @param aLine the line box containing aFrame
+ * @param aNewFrame [OUT] the new frame that was created
+ * @param aStart [IN] the start of the content mapped by aFrame (and
+ * any fluid continuations)
+ * @param aEnd [IN] the offset of the end of the single-directional
+ * text run.
+ * @see Resolve()
+ * @see RemoveBidiContinuation()
+ */
+ static inline void EnsureBidiContinuation(nsIFrame* aFrame,
+ const nsLineList::iterator aLine,
+ nsIFrame** aNewFrame,
+ int32_t aStart, int32_t aEnd);
+
+ /**
+ * Helper method for Resolve()
+ * Convert one or more bidi continuation frames created in a previous reflow
+ * by EnsureBidiContinuation() into fluid continuations.
+ * @param aFrame the frame whose continuations are to be removed
+ * @param aFirstIndex index of aFrame in mLogicalFrames
+ * @param aLastIndex index of the last frame to be removed
+ *
+ * @see Resolve()
+ * @see EnsureBidiContinuation()
+ */
+ static void RemoveBidiContinuation(BidiParagraphData* aBpd, nsIFrame* aFrame,
+ int32_t aFirstIndex, int32_t aLastIndex);
+
+ static void CalculateBidiClass(const char16_t* aText, int32_t& aOffset,
+ int32_t aBidiClassLimit, int32_t& aRunLimit,
+ int32_t& aRunLength, int32_t& aRunCount,
+ mozilla::intl::BidiClass& aBidiClass,
+ mozilla::intl::BidiClass& aPrevBidiClass);
+
+ static void StripBidiControlCharacters(char16_t* aText, int32_t& aTextLength);
+};
+
+#endif /* nsBidiPresUtils_h___ */
diff --git a/layout/base/nsCSSColorUtils.cpp b/layout/base/nsCSSColorUtils.cpp
new file mode 100644
index 0000000000..66e6a333ba
--- /dev/null
+++ b/layout/base/nsCSSColorUtils.cpp
@@ -0,0 +1,202 @@
+/* -*- 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/. */
+
+/* functions that manipulate colors */
+
+#include "nsCSSColorUtils.h"
+#include "nsDebug.h"
+#include <math.h>
+
+// Weird color computing code stolen from winfe which was stolen
+// from the xfe which was written originally by Eric Bina. So there.
+
+#define RED_LUMINOSITY 299
+#define GREEN_LUMINOSITY 587
+#define BLUE_LUMINOSITY 114
+#define INTENSITY_FACTOR 25
+#define LUMINOSITY_FACTOR 75
+
+void NS_GetSpecial3DColors(nscolor aResult[2], nscolor aBorderColor) {
+ const float kDarkerScale = 2.0f / 3.0f;
+
+ uint8_t r = NS_GET_R(aBorderColor);
+ uint8_t g = NS_GET_G(aBorderColor);
+ uint8_t b = NS_GET_B(aBorderColor);
+ uint8_t a = NS_GET_A(aBorderColor);
+ if (r == 0 && g == 0 && b == 0) {
+ // 0.3 * black
+ aResult[0] = NS_RGBA(76, 76, 76, a);
+ // 0.7 * black
+ aResult[1] = NS_RGBA(178, 178, 178, a);
+ return;
+ }
+
+ aResult[0] = NS_RGBA(uint8_t(r * kDarkerScale), uint8_t(g * kDarkerScale),
+ uint8_t(b * kDarkerScale), a);
+ aResult[1] = aBorderColor;
+}
+
+int NS_GetBrightness(uint8_t aRed, uint8_t aGreen, uint8_t aBlue) {
+ uint8_t intensity = (aRed + aGreen + aBlue) / 3;
+
+ uint8_t luminosity = NS_GetLuminosity(NS_RGB(aRed, aGreen, aBlue)) / 1000;
+
+ return ((intensity * INTENSITY_FACTOR) + (luminosity * LUMINOSITY_FACTOR)) /
+ 100;
+}
+
+int32_t NS_GetLuminosity(nscolor aColor) {
+ // When aColor is not opaque, the perceived luminosity will depend
+ // on what color(s) aColor is ultimately drawn on top of, which we
+ // do not know.
+ NS_ASSERTION(NS_GET_A(aColor) == 255,
+ "impossible to compute luminosity of a non-opaque color");
+
+ return (NS_GET_R(aColor) * RED_LUMINOSITY +
+ NS_GET_G(aColor) * GREEN_LUMINOSITY +
+ NS_GET_B(aColor) * BLUE_LUMINOSITY);
+}
+
+// Function to convert RGB color space into the HSV colorspace
+// Hue is the primary color defined from 0 to 359 degrees
+// Saturation is defined from 0 to 255. The higher the number.. the deeper
+// the color Value is the brightness of the color. 0 is black, 255 is white.
+void NS_RGB2HSV(nscolor aColor, uint16_t& aHue, uint16_t& aSat,
+ uint16_t& aValue, uint8_t& aAlpha) {
+ uint8_t r, g, b;
+ int16_t delta, min, max, r1, b1, g1;
+ float hue;
+
+ r = NS_GET_R(aColor);
+ g = NS_GET_G(aColor);
+ b = NS_GET_B(aColor);
+
+ if (r > g) {
+ max = r;
+ min = g;
+ } else {
+ max = g;
+ min = r;
+ }
+
+ if (b > max) {
+ max = b;
+ }
+ if (b < min) {
+ min = b;
+ }
+
+ // value or brightness will always be the max of all the colors(RGB)
+ aValue = max;
+ delta = max - min;
+ aSat = (max != 0) ? ((delta * 255) / max) : 0;
+ r1 = r;
+ b1 = b;
+ g1 = g;
+
+ if (aSat == 0) {
+ hue = 1000;
+ } else {
+ if (r == max) {
+ hue = (float)(g1 - b1) / (float)delta;
+ } else if (g1 == max) {
+ hue = 2.0f + (float)(b1 - r1) / (float)delta;
+ } else {
+ hue = 4.0f + (float)(r1 - g1) / (float)delta;
+ }
+ }
+
+ if (hue < 999) {
+ hue *= 60;
+ if (hue < 0) {
+ hue += 360;
+ }
+ } else {
+ hue = 0;
+ }
+
+ aHue = (uint16_t)hue;
+
+ aAlpha = NS_GET_A(aColor);
+}
+
+// Function to convert HSV color space into the RGB colorspace
+// Hue is the primary color defined from 0 to 359 degrees
+// Saturation is defined from 0 to 255. The higher the number.. the deeper
+// the color Value is the brightness of the color. 0 is black, 255 is white.
+void NS_HSV2RGB(nscolor& aColor, uint16_t aHue, uint16_t aSat, uint16_t aValue,
+ uint8_t aAlpha) {
+ uint16_t r = 0, g = 0, b = 0;
+ uint16_t i, p, q, t;
+ double h, f, percent;
+
+ if (aSat == 0) {
+ // achromatic color, no hue is defined
+ r = aValue;
+ g = aValue;
+ b = aValue;
+ } else {
+ // hue in in degrees around the color wheel defined from
+ // 0 to 360 degrees.
+ if (aHue >= 360) {
+ aHue = 0;
+ }
+
+ // we break the color wheel into 6 areas.. these
+ // areas define how the saturation and value define the color.
+ // reds behave differently than the blues
+ h = (double)aHue / 60.0;
+ i = (uint16_t)floor(h);
+ f = h - (double)i;
+ percent = ((double)aValue /
+ 255.0); // this needs to be a value from 0 to 1, so a percentage
+ // can be calculated of the saturation.
+ p = (uint16_t)(percent * (255 - aSat));
+ q = (uint16_t)(percent * (255 - (aSat * f)));
+ t = (uint16_t)(percent * (255 - (aSat * (1.0 - f))));
+
+ // i is guaranteed to never be larger than 5.
+ switch (i) {
+ case 0:
+ r = aValue;
+ g = t;
+ b = p;
+ break;
+ case 1:
+ r = q;
+ g = aValue;
+ b = p;
+ break;
+ case 2:
+ r = p;
+ g = aValue;
+ b = t;
+ break;
+ case 3:
+ r = p;
+ g = q;
+ b = aValue;
+ break;
+ case 4:
+ r = t;
+ g = p;
+ b = aValue;
+ break;
+ case 5:
+ r = aValue;
+ g = p;
+ b = q;
+ break;
+ }
+ }
+ aColor = NS_RGBA(r, g, b, aAlpha);
+}
+
+#undef RED_LUMINOSITY
+#undef GREEN_LUMINOSITY
+#undef BLUE_LUMINOSITY
+#undef INTENSITY_FACTOR
+#undef LUMINOSITY_FACTOR
diff --git a/layout/base/nsCSSColorUtils.h b/layout/base/nsCSSColorUtils.h
new file mode 100644
index 0000000000..e64241328e
--- /dev/null
+++ b/layout/base/nsCSSColorUtils.h
@@ -0,0 +1,49 @@
+/* -*- 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/. */
+
+/* functions that manipulate colors */
+
+#ifndef __nsCSSColorUtils_h
+#define __nsCSSColorUtils_h
+
+#include "nsColor.h"
+
+// "Sufficient contrast" is determined by
+// "Techniques For Accessibility Evalution And Repair Tools".
+// See http://www.w3.org/TR/AERT#color-contrast
+#define NS_SUFFICIENT_LUMINOSITY_DIFFERENCE 125000
+// NS_SUFFICIENT_LUMINOSITY_DIFFERENCE is the a11y standard for text
+// on a background. Use 20% of that standard since we have a background
+// on top of another background
+#define NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG \
+ (NS_SUFFICIENT_LUMINOSITY_DIFFERENCE / 5)
+
+#define NS_LUMINOSITY_DIFFERENCE(a, b) \
+ int32_t(mozilla::Abs(NS_GetLuminosity(a | 0xff000000) - \
+ NS_GetLuminosity(b | 0xff000000)))
+
+// Maximum value that NS_GetLuminosity can return.
+#define NS_MAX_LUMINOSITY 255000
+
+// To determine 3D colors for groove / ridge borders based on the border color
+void NS_GetSpecial3DColors(nscolor aResult[2], nscolor aBorderColor);
+
+// Determins brightness for a specific color
+int NS_GetBrightness(uint8_t aRed, uint8_t aGreen, uint8_t aBlue);
+
+// Get Luminosity of a specific color. That is same as Y of YIQ color space.
+// The range of return value is 0 to NS_MAX_LUMINOSITY.
+int32_t NS_GetLuminosity(nscolor aColor);
+
+// function to convert from RGBA color space to HSVA color space
+void NS_RGB2HSV(nscolor aColor, uint16_t& aHue, uint16_t& aSat,
+ uint16_t& aValue, uint8_t& aAlpha);
+
+// function to convert from HSVA color space to RGBA color space
+void NS_HSV2RGB(nscolor& aColor, uint16_t aHue, uint16_t aSat, uint16_t aValue,
+ uint8_t aAlpha);
+
+#endif
diff --git a/layout/base/nsCSSFrameConstructor.cpp b/layout/base/nsCSSFrameConstructor.cpp
new file mode 100644
index 0000000000..3c1896c162
--- /dev/null
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -0,0 +1,12018 @@
+/* -*- 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/. */
+
+/*
+ * construction of a frame tree that is nearly isomorphic to the content
+ * tree and updating of that tree in response to dynamic changes
+ */
+
+#include "nsCSSFrameConstructor.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/ManualNAC.h"
+#include "mozilla/dom/BindContext.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/GeneratedImageContent.h"
+#include "mozilla/dom/HTMLSelectElement.h"
+#include "mozilla/dom/HTMLSharedListElement.h"
+#include "mozilla/dom/HTMLSummaryElement.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/PrintedSheetFrame.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_mathml.h"
+#include "mozilla/Unused.h"
+#include "RetainedDisplayListBuilder.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCheckboxRadioFrame.h"
+#include "nsCRT.h"
+#include "nsAtom.h"
+#include "nsIFrameInlines.h"
+#include "nsGkAtoms.h"
+#include "nsPresContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsTableFrame.h"
+#include "nsTableColFrame.h"
+#include "nsTableRowFrame.h"
+#include "nsTableCellFrame.h"
+#include "nsFileControlFrame.h"
+#include "nsHTMLParts.h"
+#include "nsUnicharUtils.h"
+#include "nsViewManager.h"
+#include "nsStyleConsts.h"
+#include "nsXULElement.h"
+#include "nsContainerFrame.h"
+#include "nsNameSpaceManager.h"
+#include "nsComboboxControlFrame.h"
+#include "nsListControlFrame.h"
+#include "nsPlaceholderFrame.h"
+#include "nsTableRowGroupFrame.h"
+#include "nsIFormControl.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsTextFragment.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsContentUtils.h"
+#include "nsIScriptError.h"
+#ifdef XP_MACOSX
+# include "nsIDocShell.h"
+#endif
+#include "ChildIterator.h"
+#include "nsError.h"
+#include "nsLayoutUtils.h"
+#include "nsFlexContainerFrame.h"
+#include "nsGridContainerFrame.h"
+#include "RubyUtils.h"
+#include "nsRubyFrame.h"
+#include "nsRubyBaseFrame.h"
+#include "nsRubyBaseContainerFrame.h"
+#include "nsRubyTextFrame.h"
+#include "nsRubyTextContainerFrame.h"
+#include "nsImageFrame.h"
+#include "nsIObjectLoadingContent.h"
+#include "nsTArray.h"
+#include "mozilla/dom/CharacterData.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/ElementInlines.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsAutoLayoutPhase.h"
+#include "nsStyleStructInlines.h"
+#include "nsPageContentFrame.h"
+#include "mozilla/RestyleManager.h"
+#include "StickyScrollContainer.h"
+#include "nsFieldSetFrame.h"
+#include "nsInlineFrame.h"
+#include "nsBlockFrame.h"
+#include "nsCanvasFrame.h"
+#include "nsFirstLetterFrame.h"
+#include "nsGfxScrollFrame.h"
+#include "nsPageFrame.h"
+#include "nsPageSequenceFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsBackdropFrame.h"
+#include "nsTransitionManager.h"
+
+#include "nsIPopupContainer.h"
+#ifdef ACCESSIBILITY
+# include "nsAccessibilityService.h"
+#endif
+
+#undef NOISY_FIRST_LETTER
+
+#include "nsMathMLParts.h"
+#include "mozilla/SVGGradientFrame.h"
+
+#include "nsRefreshDriver.h"
+#include "nsTextNode.h"
+#include "ActiveLayerTracker.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsIFrame* NS_NewHTMLCanvasFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewHTMLVideoFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewHTMLAudioFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsContainerFrame* NS_NewSVGOuterSVGFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGOuterSVGAnonChildFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGInnerSVGFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGGeometryFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGGFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGForeignObjectFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGAFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGSwitchFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGSymbolFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGTextFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGContainerFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGUseFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGViewFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+extern nsIFrame* NS_NewSVGLinearGradientFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+extern nsIFrame* NS_NewSVGRadialGradientFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+extern nsIFrame* NS_NewSVGStopFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGMarkerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsContainerFrame* NS_NewSVGMarkerAnonChildFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+extern nsIFrame* NS_NewSVGImageFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGClipPathFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGFilterFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGPatternFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGMaskFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGFEContainerFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGFELeafFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGFEImageFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+nsIFrame* NS_NewSVGFEUnstyledLeafFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle);
+nsIFrame* NS_NewFileControlLabelFrame(PresShell*, ComputedStyle*);
+nsIFrame* NS_NewMiddleCroppingLabelFrame(PresShell*, ComputedStyle*);
+
+#include "mozilla/dom/NodeInfo.h"
+#include "prenv.h"
+#include "nsNodeInfoManager.h"
+#include "nsContentCreatorFunctions.h"
+
+#ifdef DEBUG
+// Set the environment variable GECKO_FRAMECTOR_DEBUG_FLAGS to one or
+// more of the following flags (comma separated) for handy debug
+// output.
+static bool gNoisyContentUpdates = false;
+static bool gReallyNoisyContentUpdates = false;
+static bool gNoisyInlineConstruction = false;
+
+struct FrameCtorDebugFlags {
+ const char* name;
+ bool* on;
+};
+
+static FrameCtorDebugFlags gFlags[] = {
+ {"content-updates", &gNoisyContentUpdates},
+ {"really-noisy-content-updates", &gReallyNoisyContentUpdates},
+ {"noisy-inline", &gNoisyInlineConstruction}};
+
+# define NUM_DEBUG_FLAGS (sizeof(gFlags) / sizeof(gFlags[0]))
+#endif
+
+//------------------------------------------------------------------
+
+nsIFrame* NS_NewLeafBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewRangeFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewTextBoxFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewSplitterFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewMenuPopupFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewTreeBodyFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsHTMLScrollFrame* NS_NewHTMLScrollFrame(PresShell* aPresShell,
+ ComputedStyle* aStyle, bool aIsRoot);
+
+nsIFrame* NS_NewSliderFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewScrollbarFrame(PresShell* aPresShell, ComputedStyle* aStyle);
+
+nsIFrame* NS_NewScrollbarButtonFrame(PresShell*, ComputedStyle*);
+nsIFrame* NS_NewSimpleXULLeafFrame(PresShell*, ComputedStyle*);
+
+nsIFrame* NS_NewXULImageFrame(PresShell*, ComputedStyle*);
+nsIFrame* NS_NewImageFrameForContentProperty(PresShell*, ComputedStyle*);
+nsIFrame* NS_NewImageFrameForGeneratedContentIndex(PresShell*, ComputedStyle*);
+nsIFrame* NS_NewImageFrameForListStyleImage(PresShell*, ComputedStyle*);
+
+// Returns true if aFrame is an anonymous flex/grid item.
+static inline bool IsAnonymousItem(const nsIFrame* aFrame) {
+ return aFrame->Style()->GetPseudoType() == PseudoStyleType::anonymousItem;
+}
+
+// Returns true IFF the given nsIFrame is a nsFlexContainerFrame and represents
+// a -webkit-{inline-}box container.
+static inline bool IsFlexContainerForLegacyWebKitBox(const nsIFrame* aFrame) {
+ return aFrame->IsFlexContainerFrame() &&
+ aFrame->HasAnyStateBits(NS_STATE_FLEX_IS_EMULATING_LEGACY_WEBKIT_BOX);
+}
+
+#if DEBUG
+static void AssertAnonymousFlexOrGridItemParent(const nsIFrame* aChild,
+ const nsIFrame* aParent) {
+ MOZ_ASSERT(IsAnonymousItem(aChild), "expected an anonymous item child frame");
+ MOZ_ASSERT(aParent, "expected a parent frame");
+ MOZ_ASSERT(aParent->IsFlexOrGridContainer(),
+ "anonymous items should only exist as children of flex/grid "
+ "container frames");
+}
+#else
+# define AssertAnonymousFlexOrGridItemParent(x, y) PR_BEGIN_MACRO PR_END_MACRO
+#endif
+
+#define ToCreationFunc(_func) \
+ [](PresShell* aPs, ComputedStyle* aStyle) -> nsIFrame* { \
+ return _func(aPs, aStyle); \
+ }
+
+/**
+ * True if aFrame is an actual inline frame in the sense of non-replaced
+ * display:inline CSS boxes. In other words, it can be affected by {ib}
+ * splitting and can contain first-letter frames. Basically, this is either an
+ * inline frame (positioned or otherwise) or an line frame (this last because
+ * it can contain first-letter and because inserting blocks in the middle of it
+ * needs to terminate it).
+ */
+static bool IsInlineFrame(const nsIFrame* aFrame) {
+ return aFrame->IsLineParticipant();
+}
+
+/**
+ * True for display: contents elements.
+ */
+static inline bool IsDisplayContents(const Element* aElement) {
+ return aElement->IsDisplayContents();
+}
+
+static inline bool IsDisplayContents(const nsIContent* aContent) {
+ return aContent->IsElement() && IsDisplayContents(aContent->AsElement());
+}
+
+/**
+ * True if aFrame is an instance of an SVG frame class or is an inline/block
+ * frame being used for SVG text.
+ */
+static bool IsFrameForSVG(const nsIFrame* aFrame) {
+ return aFrame->IsSVGFrame() || aFrame->IsInSVGTextSubtree();
+}
+
+static bool IsLastContinuationForColumnContent(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->Style()->GetPseudoType() == PseudoStyleType::columnContent &&
+ !aFrame->GetNextContinuation();
+}
+
+/**
+ * Returns true iff aFrame explicitly prevents its descendants from floating
+ * (at least, down to the level of descendants which themselves are
+ * float-containing blocks -- those will manage the floating status of any
+ * lower-level descendents inside them, of course).
+ */
+static bool ShouldSuppressFloatingOfDescendants(nsIFrame* aFrame) {
+ return aFrame->IsFlexOrGridContainer() || aFrame->IsMathMLFrame();
+}
+
+// Return true if column-span descendants should be suppressed under aFrame's
+// subtree (until a multi-column container re-establishing a block formatting
+// context). Basically, this is testing whether aFrame establishes a new block
+// formatting context or not.
+static bool ShouldSuppressColumnSpanDescendants(nsIFrame* aFrame) {
+ if (aFrame->Style()->GetPseudoType() == PseudoStyleType::columnContent) {
+ // Never suppress column-span under ::-moz-column-content frames.
+ return false;
+ }
+
+ if (aFrame->IsInlineFrame()) {
+ // Allow inline frames to have column-span block children.
+ return false;
+ }
+
+ if (!aFrame->IsBlockFrameOrSubclass() ||
+ aFrame->HasAnyStateBits(NS_BLOCK_BFC_STATE_BITS | NS_FRAME_OUT_OF_FLOW) ||
+ aFrame->IsFixedPosContainingBlock()) {
+ // Need to suppress column-span if we:
+ // - Are a different block formatting context,
+ // - Are an out-of-flow frame, OR
+ // - Establish a containing block for fixed-position descendants
+ //
+ // For example, the children of a column-span never need to be further
+ // processed even if there is a nested column-span child. Because a
+ // column-span always creates its own block formatting context, a nested
+ // column-span child won't be in the same block formatting context with the
+ // nearest multi-column ancestor. This is the same case as if the
+ // column-span is outside of a multi-column hierarchy.
+ return true;
+ }
+
+ return false;
+}
+
+// Reparent a frame into a wrapper frame that is a child of its old parent.
+static void ReparentFrame(RestyleManager* aRestyleManager,
+ nsContainerFrame* aNewParentFrame, nsIFrame* aFrame,
+ bool aForceStyleReparent) {
+ aFrame->SetParent(aNewParentFrame);
+ // We reparent frames for two reasons: to put them inside ::first-line, and to
+ // put them inside some wrapper anonymous boxes.
+ if (aForceStyleReparent) {
+ aRestyleManager->ReparentComputedStyleForFirstLine(aFrame);
+ }
+}
+
+static void ReparentFrames(nsCSSFrameConstructor* aFrameConstructor,
+ nsContainerFrame* aNewParentFrame,
+ const nsFrameList& aFrameList,
+ bool aForceStyleReparent) {
+ RestyleManager* restyleManager = aFrameConstructor->RestyleManager();
+ for (nsIFrame* f : aFrameList) {
+ ReparentFrame(restyleManager, aNewParentFrame, f, aForceStyleReparent);
+ }
+}
+
+//----------------------------------------------------------------------
+//
+// When inline frames get weird and have block frames in them, we
+// annotate them to help us respond to incremental content changes
+// more easily.
+
+static inline bool IsFramePartOfIBSplit(nsIFrame* aFrame) {
+ bool result = aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT);
+ MOZ_ASSERT(!result || static_cast<nsBlockFrame*>(do_QueryFrame(aFrame)) ||
+ static_cast<nsInlineFrame*>(do_QueryFrame(aFrame)),
+ "only block/inline frames can have NS_FRAME_PART_OF_IBSPLIT");
+ return result;
+}
+
+static nsContainerFrame* GetIBSplitSibling(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
+
+ // We only store the "ib-split sibling" annotation with the first
+ // frame in the continuation chain. Walk back to find that frame now.
+ return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
+}
+
+static nsContainerFrame* GetIBSplitPrevSibling(nsIFrame* aFrame) {
+ MOZ_ASSERT(IsFramePartOfIBSplit(aFrame), "Shouldn't call this");
+
+ // We only store the ib-split sibling annotation with the first
+ // frame in the continuation chain. Walk back to find that frame now.
+ return aFrame->FirstContinuation()->GetProperty(
+ nsIFrame::IBSplitPrevSibling());
+}
+
+static nsContainerFrame* GetLastIBSplitSibling(nsIFrame* aFrame) {
+ for (nsIFrame *frame = aFrame, *next;; frame = next) {
+ next = GetIBSplitSibling(frame);
+ if (!next) {
+ return static_cast<nsContainerFrame*>(frame);
+ }
+ }
+ MOZ_ASSERT_UNREACHABLE("unreachable code");
+ return nullptr;
+}
+
+static void SetFrameIsIBSplit(nsContainerFrame* aFrame,
+ nsContainerFrame* aIBSplitSibling) {
+ MOZ_ASSERT(aFrame, "bad args!");
+
+ // We should be the only continuation
+ NS_ASSERTION(!aFrame->GetPrevContinuation(),
+ "assigning ib-split sibling to other than first continuation!");
+ NS_ASSERTION(!aFrame->GetNextContinuation() ||
+ IsFramePartOfIBSplit(aFrame->GetNextContinuation()),
+ "should have no non-ib-split continuations here");
+
+ // Mark the frame as ib-split.
+ aFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT);
+
+ if (aIBSplitSibling) {
+ NS_ASSERTION(!aIBSplitSibling->GetPrevContinuation(),
+ "assigning something other than the first continuation as the "
+ "ib-split sibling");
+
+ // Store the ib-split sibling (if we were given one) with the
+ // first frame in the flow.
+ aFrame->SetProperty(nsIFrame::IBSplitSibling(), aIBSplitSibling);
+ aIBSplitSibling->SetProperty(nsIFrame::IBSplitPrevSibling(), aFrame);
+ }
+}
+
+static nsIFrame* GetIBContainingBlockFor(nsIFrame* aFrame) {
+ MOZ_ASSERT(
+ IsFramePartOfIBSplit(aFrame),
+ "GetIBContainingBlockFor() should only be called on known IB frames");
+
+ // Get the first "normal" ancestor of the target frame.
+ nsIFrame* parentFrame;
+ do {
+ parentFrame = aFrame->GetParent();
+
+ if (!parentFrame) {
+ NS_ERROR("no unsplit block frame in IB hierarchy");
+ return aFrame;
+ }
+
+ // Note that we ignore non-ib-split frames which have a pseudo on their
+ // ComputedStyle -- they're not the frames we're looking for! In
+ // particular, they may be hiding a real parent that _is_ in an ib-split.
+ if (!IsFramePartOfIBSplit(parentFrame) &&
+ !parentFrame->Style()->IsPseudoOrAnonBox())
+ break;
+
+ aFrame = parentFrame;
+ } while (1);
+
+ // post-conditions
+ NS_ASSERTION(parentFrame,
+ "no normal ancestor found for ib-split frame "
+ "in GetIBContainingBlockFor");
+ NS_ASSERTION(parentFrame != aFrame,
+ "parentFrame is actually the child frame - bogus reslt");
+
+ return parentFrame;
+}
+
+// Find the multicol containing block suitable for reframing.
+//
+// Note: this function may not return a ColumnSetWrapperFrame. For example, if
+// the multicol containing block has "overflow:scroll" style, HTMLScrollFrame is
+// returned because ColumnSetWrapperFrame is the scrolled frame which has the
+// -moz-scrolled-content pseudo style. We may walk up "too far", but in terms of
+// correctness of reframing, it's OK.
+static nsContainerFrame* GetMultiColumnContainingBlockFor(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
+ "Should only be called if the frame has a multi-column ancestor!");
+
+ nsContainerFrame* current = aFrame->GetParent();
+ while (current &&
+ (current->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+ current->Style()->IsPseudoOrAnonBox())) {
+ current = current->GetParent();
+ }
+
+ MOZ_ASSERT(current,
+ "No multicol containing block in a valid column hierarchy?");
+
+ return current;
+}
+
+// This is a bit slow, but sometimes we need it.
+static bool ParentIsWrapperAnonBox(nsIFrame* aParent) {
+ nsIFrame* maybeAnonBox = aParent;
+ if (maybeAnonBox->Style()->GetPseudoType() == PseudoStyleType::cellContent) {
+ // The thing that would maybe be a wrapper anon box is the cell.
+ maybeAnonBox = maybeAnonBox->GetParent();
+ }
+ return maybeAnonBox->Style()->IsWrapperAnonBox();
+}
+
+static bool InsertSeparatorBeforeAccessKey() {
+ static bool sInitialized = false;
+ static bool sValue = false;
+ if (!sInitialized) {
+ sInitialized = true;
+
+ const char* prefName = "intl.menuitems.insertseparatorbeforeaccesskeys";
+ nsAutoString val;
+ Preferences::GetLocalizedString(prefName, val);
+ sValue = val.EqualsLiteral("true");
+ }
+ return sValue;
+}
+
+static bool AlwaysAppendAccessKey() {
+ static bool sInitialized = false;
+ static bool sValue = false;
+ if (!sInitialized) {
+ sInitialized = true;
+ const char* prefName = "intl.menuitems.alwaysappendaccesskeys";
+ nsAutoString val;
+ Preferences::GetLocalizedString(prefName, val);
+ sValue = val.EqualsLiteral("true");
+ }
+ return sValue;
+}
+
+//----------------------------------------------------------------------
+
+// Block/inline frame construction logic. We maintain a few invariants here:
+//
+// 1. Block frames contain block and inline frames.
+//
+// 2. Inline frames only contain inline frames. If an inline parent has a block
+// child then the block child is migrated upward until it lands in a block
+// parent (the inline frames containing block is where it will end up).
+
+inline void SetInitialSingleChild(nsContainerFrame* aParent, nsIFrame* aFrame) {
+ MOZ_ASSERT(!aFrame->GetNextSibling(), "Should be using a frame list");
+ aParent->SetInitialChildList(FrameChildListID::Principal,
+ nsFrameList(aFrame, aFrame));
+}
+
+// -----------------------------------------------------------
+
+// Structure used when constructing formatting object trees. Contains
+// state information needed for absolutely positioned elements
+namespace mozilla {
+struct AbsoluteFrameList final : public nsFrameList {
+ // Containing block for absolutely positioned elements.
+ nsContainerFrame* mContainingBlock;
+
+ explicit AbsoluteFrameList(nsContainerFrame* aContainingBlock = nullptr)
+ : mContainingBlock(aContainingBlock) {}
+
+ // Transfer frames in aOther to this list. aOther becomes empty after this
+ // operation.
+ AbsoluteFrameList(AbsoluteFrameList&& aOther) = default;
+ AbsoluteFrameList& operator=(AbsoluteFrameList&& aOther) = default;
+
+#ifdef DEBUG
+ // XXXbz Does this need a debug-only assignment operator that nulls out the
+ // childList in the AbsoluteFrameList we're copying? Introducing a difference
+ // between debug and non-debug behavior seems bad, so I guess not...
+ ~AbsoluteFrameList() {
+ NS_ASSERTION(!FirstChild(),
+ "Dangling child list. Someone forgot to insert it?");
+ }
+#endif
+};
+} // namespace mozilla
+
+// -----------------------------------------------------------
+
+// Structure for saving the existing state when pushing/poping containing
+// blocks. The destructor restores the state to its previous state
+class MOZ_STACK_CLASS nsFrameConstructorSaveState {
+ public:
+ ~nsFrameConstructorSaveState();
+
+ private:
+ // Pointer to struct whose data we save/restore.
+ AbsoluteFrameList* mList = nullptr;
+
+ // The saved pointer to the fixed list.
+ AbsoluteFrameList* mSavedFixedList = nullptr;
+
+ // Copy of original frame list. This can be the original absolute list or a
+ // float list.
+ AbsoluteFrameList mSavedList;
+
+ // The name of the child list in which our frames would belong.
+ mozilla::FrameChildListID mChildListID = FrameChildListID::Principal;
+ nsFrameConstructorState* mState = nullptr;
+
+ friend class nsFrameConstructorState;
+};
+
+// Structure used for maintaining state information during the
+// frame construction process
+class MOZ_STACK_CLASS nsFrameConstructorState {
+ public:
+ nsPresContext* mPresContext;
+ PresShell* mPresShell;
+ nsCSSFrameConstructor* mFrameConstructor;
+
+ // Containing block information for out-of-flow frames.
+ //
+ // Floats are easy. Whatever is our float CB.
+ //
+ // Regular abspos elements are easy too. Its containing block can be the
+ // nearest abspos element, or the ICB (the canvas frame).
+ //
+ // Top layer abspos elements are always children of the ICB, but we can get
+ // away with having two different lists (mAbsoluteList and
+ // mTopLayerAbsoluteList), because because top layer frames cause
+ // non-top-layer frames to be contained inside (so any descendants of a top
+ // layer abspos can never share containing block with it, unless they're also
+ // in the top layer).
+ //
+ // Regular fixed elements however are trickier. Fixed elements can be
+ // contained in one of three lists:
+ //
+ // * mAbsoluteList, if our abspos cb is also a fixpos cb (e.g., is
+ // transformed or has a filter).
+ //
+ // * mAncestorFixedList, if the fixpos cb is an ancestor element other than
+ // the viewport frame, (so, a transformed / filtered
+ // ancestor).
+ //
+ // * mRealFixedList, which is also the fixed list used for the top layer
+ // fixed items, which is the fixed list of the viewport
+ // frame.
+ //
+ // It is important that mRealFixedList is shared between regular and top layer
+ // fixpos elements, since no-top-layer descendants of top layer fixed elements
+ // could share ICB and vice versa, so without that there would be no guarantee
+ // of layout ordering between them.
+ AbsoluteFrameList mFloatedList;
+ AbsoluteFrameList mAbsoluteList;
+ AbsoluteFrameList mTopLayerAbsoluteList;
+ AbsoluteFrameList mAncestorFixedList;
+ AbsoluteFrameList mRealFixedList;
+
+ // Never null, always pointing to one of the lists documented above.
+ AbsoluteFrameList* mFixedList;
+
+ // What `page: auto` resolves to. This is the used page-name of the parent
+ // frame. Updated by AutoFrameConstructionPageName.
+ const nsAtom* mAutoPageNameValue = nullptr;
+
+ nsCOMPtr<nsILayoutHistoryState> mFrameState;
+ // These bits will be added to the state bits of any frame we construct
+ // using this state.
+ nsFrameState mAdditionalStateBits{0};
+
+ // If false (which is the default) then call SetPrimaryFrame() as needed
+ // during frame construction. If true, don't make any SetPrimaryFrame()
+ // calls, except for generated content which doesn't have a primary frame
+ // yet. The mCreatingExtraFrames == true mode is meant to be used for
+ // construction of random "extra" frames for elements via normal frame
+ // construction APIs (e.g. replication of things across pages in paginated
+ // mode).
+ bool mCreatingExtraFrames;
+
+ // This keeps track of whether we have found a "rendered legend" for
+ // the current FieldSetFrame.
+ bool mHasRenderedLegend;
+
+ nsTArray<RefPtr<nsIContent>> mGeneratedContentWithInitializer;
+
+#ifdef DEBUG
+ // Record the float containing block candidate passed into
+ // MaybePushFloatContainingBlock() to keep track that we've call the method to
+ // handle the float CB scope before processing the CB's children. It is reset
+ // in ConstructFramesFromItemList().
+ nsContainerFrame* mFloatCBCandidate = nullptr;
+#endif
+
+ // Constructor
+ // Use the passed-in history state.
+ nsFrameConstructorState(
+ PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock,
+ already_AddRefed<nsILayoutHistoryState> aHistoryState);
+ // Get the history state from the pres context's pres shell.
+ nsFrameConstructorState(PresShell* aPresShell,
+ nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock);
+
+ ~nsFrameConstructorState();
+
+ // Process the frame insertions for all the out-of-flow nsAbsoluteItems.
+ void ProcessFrameInsertionsForAllLists();
+
+ // Function to push the existing absolute containing block state and
+ // create a new scope. Code that uses this function should get matching
+ // logic in GetAbsoluteContainingBlock.
+ // Also makes aNewAbsoluteContainingBlock the containing block for
+ // fixed-pos elements if necessary.
+ // aPositionedFrame is the frame whose style actually makes
+ // aNewAbsoluteContainingBlock a containing block. E.g. for a scrollable
+ // element aPositionedFrame is the element's primary frame and
+ // aNewAbsoluteContainingBlock is the scrolled frame.
+ void PushAbsoluteContainingBlock(
+ nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame,
+ nsFrameConstructorSaveState& aSaveState);
+
+ // Function to forbid floats descendants under aFloatCBCandidate, or open a
+ // new float containing block scope for aFloatCBCandidate. The current
+ // state is saved in aSaveState if a new scope is pushed.
+ void MaybePushFloatContainingBlock(nsContainerFrame* aFloatCBCandidate,
+ nsFrameConstructorSaveState& aSaveState);
+
+ // Helper function for MaybePushFloatContainingBlock().
+ void PushFloatContainingBlock(nsContainerFrame* aNewFloatContainingBlock,
+ nsFrameConstructorSaveState& aSaveState);
+
+ // Function to return the proper geometric parent for a frame with display
+ // struct given by aStyleDisplay and parent's frame given by
+ // aContentParentFrame.
+ nsContainerFrame* GetGeometricParent(
+ const nsStyleDisplay& aStyleDisplay,
+ nsContainerFrame* aContentParentFrame) const;
+
+ // Collect absolute frames in mAbsoluteList which are proper descendants
+ // of aNewParent, and reparent them to aNewParent.
+ //
+ // Note: This function does something unusual that moves absolute items
+ // after their frames are constructed under a column hierarchy which has
+ // column-span elements. Do not use this if you're not dealing with
+ // columns.
+ void ReparentAbsoluteItems(nsContainerFrame* aNewParent);
+
+ // Collect floats in mFloatedList which are proper descendants of aNewParent,
+ // and reparent them to aNewParent.
+ //
+ // Note: This function does something unusual that moves floats after their
+ // frames are constructed under a column hierarchy which has column-span
+ // elements. Do not use this if you're not dealing with columns.
+ void ReparentFloats(nsContainerFrame* aNewParent);
+
+ /**
+ * Function to add a new frame to the right frame list. This MUST be called
+ * on frames before their children have been processed if the frames might
+ * conceivably be out-of-flow; otherwise cleanup in error cases won't work
+ * right. Also, this MUST be called on frames after they have been
+ * initialized.
+ * @param aNewFrame the frame to add
+ * @param aFrameList the list to add in-flow frames to
+ * @param aContent the content pointer for aNewFrame
+ * @param aParentFrame the parent frame for the content if it were in-flow
+ * @param aCanBePositioned pass false if the frame isn't allowed to be
+ * positioned
+ * @param aCanBeFloated pass false if the frame isn't allowed to be
+ * floated
+ */
+ void AddChild(nsIFrame* aNewFrame, nsFrameList& aFrameList,
+ nsIContent* aContent, nsContainerFrame* aParentFrame,
+ bool aCanBePositioned = true, bool aCanBeFloated = true,
+ bool aInsertAfter = false,
+ nsIFrame* aInsertAfterFrame = nullptr);
+
+ /**
+ * Function to return the fixed-pos element list. Normally this will just
+ * hand back the fixed-pos element list, but in case we're dealing with a
+ * transformed element that's acting as an abs-pos and fixed-pos container,
+ * we'll hand back the abs-pos list. Callers should use this function if they
+ * want to get the list acting as the fixed-pos item parent.
+ */
+ AbsoluteFrameList& GetFixedList() { return *mFixedList; }
+ const AbsoluteFrameList& GetFixedList() const { return *mFixedList; }
+
+ protected:
+ friend class nsFrameConstructorSaveState;
+
+ /**
+ * ProcessFrameInsertions takes the frames in aFrameList and adds them as
+ * kids to the aChildListID child list of |aFrameList.containingBlock|.
+ */
+ void ProcessFrameInsertions(AbsoluteFrameList& aFrameList,
+ mozilla::FrameChildListID aChildListID);
+
+ /**
+ * GetOutOfFlowFrameList selects the out-of-flow frame list the new
+ * frame should be added to. If the frame shouldn't be added to any
+ * out-of-flow list, it returns nullptr. The corresponding type of
+ * placeholder is also returned via the aPlaceholderType parameter
+ * if this method doesn't return nullptr. The caller should check
+ * whether the returned list really has a containing block.
+ */
+ AbsoluteFrameList* GetOutOfFlowFrameList(nsIFrame* aNewFrame,
+ bool aCanBePositioned,
+ bool aCanBeFloated,
+ nsFrameState* aPlaceholderType);
+
+ void ConstructBackdropFrameFor(nsIContent* aContent, nsIFrame* aFrame);
+};
+
+nsFrameConstructorState::nsFrameConstructorState(
+ PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock,
+ already_AddRefed<nsILayoutHistoryState> aHistoryState)
+ : mPresContext(aPresShell->GetPresContext()),
+ mPresShell(aPresShell),
+ mFrameConstructor(aPresShell->FrameConstructor()),
+ mFloatedList(aFloatContainingBlock),
+ mAbsoluteList(aAbsoluteContainingBlock),
+ mTopLayerAbsoluteList(mFrameConstructor->GetCanvasFrame()),
+ mAncestorFixedList(aFixedContainingBlock),
+ mRealFixedList(
+ static_cast<nsContainerFrame*>(mFrameConstructor->GetRootFrame())),
+ // See PushAbsoluteContaningBlock below
+ mFrameState(aHistoryState),
+ mCreatingExtraFrames(false),
+ mHasRenderedLegend(false) {
+ MOZ_COUNT_CTOR(nsFrameConstructorState);
+ mFixedList = [&] {
+ if (aFixedContainingBlock == aAbsoluteContainingBlock) {
+ return &mAbsoluteList;
+ }
+ if (aAbsoluteContainingBlock == mRealFixedList.mContainingBlock) {
+ return &mRealFixedList;
+ }
+ return &mAncestorFixedList;
+ }();
+}
+
+nsFrameConstructorState::nsFrameConstructorState(
+ PresShell* aPresShell, nsContainerFrame* aFixedContainingBlock,
+ nsContainerFrame* aAbsoluteContainingBlock,
+ nsContainerFrame* aFloatContainingBlock)
+ : nsFrameConstructorState(
+ aPresShell, aFixedContainingBlock, aAbsoluteContainingBlock,
+ aFloatContainingBlock,
+ aPresShell->GetDocument()->GetLayoutHistoryState()) {}
+
+nsFrameConstructorState::~nsFrameConstructorState() {
+ MOZ_COUNT_DTOR(nsFrameConstructorState);
+ ProcessFrameInsertionsForAllLists();
+ for (auto& content : Reversed(mGeneratedContentWithInitializer)) {
+ content->RemoveProperty(nsGkAtoms::genConInitializerProperty);
+ }
+}
+
+void nsFrameConstructorState::ProcessFrameInsertionsForAllLists() {
+ ProcessFrameInsertions(mFloatedList, FrameChildListID::Float);
+ ProcessFrameInsertions(mAbsoluteList, FrameChildListID::Absolute);
+ ProcessFrameInsertions(mTopLayerAbsoluteList, FrameChildListID::Absolute);
+ ProcessFrameInsertions(*mFixedList, FrameChildListID::Fixed);
+ ProcessFrameInsertions(mRealFixedList, FrameChildListID::Fixed);
+}
+
+void nsFrameConstructorState::PushAbsoluteContainingBlock(
+ nsContainerFrame* aNewAbsoluteContainingBlock, nsIFrame* aPositionedFrame,
+ nsFrameConstructorSaveState& aSaveState) {
+ MOZ_ASSERT(!!aNewAbsoluteContainingBlock == !!aPositionedFrame,
+ "We should have both or none");
+ aSaveState.mList = &mAbsoluteList;
+ aSaveState.mChildListID = FrameChildListID::Absolute;
+ aSaveState.mState = this;
+ aSaveState.mSavedList = std::move(mAbsoluteList);
+ aSaveState.mSavedFixedList = mFixedList;
+ mAbsoluteList = AbsoluteFrameList(aNewAbsoluteContainingBlock);
+ mFixedList = [&] {
+ if (!aPositionedFrame || aPositionedFrame->IsFixedPosContainingBlock()) {
+ // See if we need to treat abspos and fixedpos the same. This happens if
+ // we're a transformed/filtered/etc element, or if we force a null abspos
+ // containing block (for mathml for example).
+ return &mAbsoluteList;
+ }
+ if (aPositionedFrame->StyleDisplay()->mTopLayer == StyleTopLayer::Top) {
+ // If our new CB is in the top layer, and isn't a fixed CB itself, we also
+ // escape the usual containment.
+ return &mRealFixedList;
+ }
+ if (mFixedList == &mAbsoluteList) {
+ // If we were pointing to our old absolute list, keep pointing to it.
+ return &aSaveState.mSavedList;
+ }
+ // Otherwise keep pointing to the current thing (another ancestor's
+ // absolute list, or the real fixed list, doesn't matter).
+ return mFixedList;
+ }();
+
+ if (aNewAbsoluteContainingBlock) {
+ aNewAbsoluteContainingBlock->MarkAsAbsoluteContainingBlock();
+ }
+}
+
+void nsFrameConstructorState::MaybePushFloatContainingBlock(
+ nsContainerFrame* aFloatCBCandidate,
+ nsFrameConstructorSaveState& aSaveState) {
+ // The logic here needs to match the logic in GetFloatContainingBlock().
+ if (ShouldSuppressFloatingOfDescendants(aFloatCBCandidate)) {
+ // Pushing a null float containing block forbids any frames from being
+ // floated until a new float containing block is pushed. See implementation
+ // of nsFrameConstructorState::AddChild().
+ //
+ // XXX we should get rid of null float containing blocks and teach the
+ // various frame classes to deal with floats instead.
+ PushFloatContainingBlock(nullptr, aSaveState);
+ } else if (aFloatCBCandidate->IsFloatContainingBlock()) {
+ PushFloatContainingBlock(aFloatCBCandidate, aSaveState);
+ }
+
+#ifdef DEBUG
+ mFloatCBCandidate = aFloatCBCandidate;
+#endif
+}
+
+void nsFrameConstructorState::PushFloatContainingBlock(
+ nsContainerFrame* aNewFloatContainingBlock,
+ nsFrameConstructorSaveState& aSaveState) {
+ MOZ_ASSERT(!aNewFloatContainingBlock ||
+ aNewFloatContainingBlock->IsFloatContainingBlock(),
+ "Please push a real float containing block!");
+ NS_ASSERTION(
+ !aNewFloatContainingBlock ||
+ !ShouldSuppressFloatingOfDescendants(aNewFloatContainingBlock),
+ "We should not push a frame that is supposed to _suppress_ "
+ "floats as a float containing block!");
+ aSaveState.mList = &mFloatedList;
+ aSaveState.mSavedList = std::move(mFloatedList);
+ aSaveState.mChildListID = FrameChildListID::Float;
+ aSaveState.mState = this;
+ mFloatedList = AbsoluteFrameList(aNewFloatContainingBlock);
+}
+
+nsContainerFrame* nsFrameConstructorState::GetGeometricParent(
+ const nsStyleDisplay& aStyleDisplay,
+ nsContainerFrame* aContentParentFrame) const {
+ // If there is no container for a fixed, absolute, or floating root
+ // frame, we will ignore the positioning. This hack is originally
+ // brought to you by the letter T: tables, since other roots don't
+ // even call into this code. See bug 178855.
+ //
+ // XXX Disabling positioning in this case is a hack. If one was so inclined,
+ // one could support this either by (1) inserting a dummy block between the
+ // table and the canvas or (2) teaching the canvas how to reflow positioned
+ // elements. (1) has the usual problems when multiple frames share the same
+ // content (notice all the special cases in this file dealing with inner
+ // tables and table wrappers which share the same content). (2) requires some
+ // work and possible factoring.
+ //
+ // XXXbz couldn't we just force position to "static" on roots and
+ // float to "none"? That's OK per CSS 2.1, as far as I can tell.
+
+ if (aContentParentFrame && aContentParentFrame->IsInSVGTextSubtree()) {
+ return aContentParentFrame;
+ }
+
+ if (aStyleDisplay.IsFloatingStyle() && mFloatedList.mContainingBlock) {
+ NS_ASSERTION(!aStyleDisplay.IsAbsolutelyPositionedStyle(),
+ "Absolutely positioned _and_ floating?");
+ return mFloatedList.mContainingBlock;
+ }
+
+ if (aStyleDisplay.mTopLayer != StyleTopLayer::None) {
+ MOZ_ASSERT(aStyleDisplay.mTopLayer == StyleTopLayer::Top,
+ "-moz-top-layer should be either none or top");
+ MOZ_ASSERT(aStyleDisplay.IsAbsolutelyPositionedStyle(),
+ "Top layer items should always be absolutely positioned");
+ if (aStyleDisplay.mPosition == StylePositionProperty::Fixed) {
+ MOZ_ASSERT(mRealFixedList.mContainingBlock, "No root frame?");
+ return mRealFixedList.mContainingBlock;
+ }
+ MOZ_ASSERT(aStyleDisplay.mPosition == StylePositionProperty::Absolute);
+ MOZ_ASSERT(mTopLayerAbsoluteList.mContainingBlock);
+ return mTopLayerAbsoluteList.mContainingBlock;
+ }
+
+ if (aStyleDisplay.mPosition == StylePositionProperty::Absolute &&
+ mAbsoluteList.mContainingBlock) {
+ return mAbsoluteList.mContainingBlock;
+ }
+
+ if (aStyleDisplay.mPosition == StylePositionProperty::Fixed &&
+ mFixedList->mContainingBlock) {
+ return mFixedList->mContainingBlock;
+ }
+
+ return aContentParentFrame;
+}
+
+void nsFrameConstructorState::ReparentAbsoluteItems(
+ nsContainerFrame* aNewParent) {
+ // Bug 1491727: This function might not conform to the spec. See
+ // https://github.com/w3c/csswg-drafts/issues/1894.
+
+ MOZ_ASSERT(aNewParent->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
+ "Restrict the usage under column hierarchy.");
+
+ AbsoluteFrameList newAbsoluteItems(aNewParent);
+
+ nsIFrame* current = mAbsoluteList.FirstChild();
+ while (current) {
+ nsIFrame* placeholder = current->GetPlaceholderFrame();
+
+ if (nsLayoutUtils::IsProperAncestorFrame(aNewParent, placeholder)) {
+ nsIFrame* next = current->GetNextSibling();
+ mAbsoluteList.RemoveFrame(current);
+ newAbsoluteItems.AppendFrame(aNewParent, current);
+ current = next;
+ } else {
+ current = current->GetNextSibling();
+ }
+ }
+
+ if (newAbsoluteItems.NotEmpty()) {
+ // ~nsFrameConstructorSaveState() will move newAbsoluteItems to
+ // aNewParent's absolute child list.
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ // It doesn't matter whether aNewParent has position style or not. Caller
+ // won't call us if we can't have absolute children.
+ PushAbsoluteContainingBlock(aNewParent, aNewParent, absoluteSaveState);
+ mAbsoluteList = std::move(newAbsoluteItems);
+ }
+}
+
+void nsFrameConstructorState::ReparentFloats(nsContainerFrame* aNewParent) {
+ MOZ_ASSERT(aNewParent->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR),
+ "Restrict the usage under column hierarchy.");
+ MOZ_ASSERT(
+ aNewParent->IsFloatContainingBlock(),
+ "Why calling this method if aNewParent is not a float containing block?");
+
+ // Gather floats that should reparent under aNewParent.
+ AbsoluteFrameList floats(aNewParent);
+ nsIFrame* current = mFloatedList.FirstChild();
+ while (current) {
+ nsIFrame* placeholder = current->GetPlaceholderFrame();
+ nsIFrame* next = current->GetNextSibling();
+ if (nsLayoutUtils::IsProperAncestorFrame(aNewParent, placeholder)) {
+ mFloatedList.RemoveFrame(current);
+ floats.AppendFrame(aNewParent, current);
+ }
+ current = next;
+ }
+
+ if (floats.NotEmpty()) {
+ // Make floats move into aNewParent's float child list in
+ // ~nsFrameConstructorSaveState() when destructing floatSaveState.
+ nsFrameConstructorSaveState floatSaveState;
+ PushFloatContainingBlock(aNewParent, floatSaveState);
+ mFloatedList = std::move(floats);
+ }
+}
+
+AbsoluteFrameList* nsFrameConstructorState::GetOutOfFlowFrameList(
+ nsIFrame* aNewFrame, bool aCanBePositioned, bool aCanBeFloated,
+ nsFrameState* aPlaceholderType) {
+ const nsStyleDisplay* disp = aNewFrame->StyleDisplay();
+ if (aCanBeFloated && disp->IsFloatingStyle()) {
+ *aPlaceholderType = PLACEHOLDER_FOR_FLOAT;
+ return &mFloatedList;
+ }
+
+ if (aCanBePositioned) {
+ if (disp->mTopLayer != StyleTopLayer::None) {
+ *aPlaceholderType = PLACEHOLDER_FOR_TOPLAYER;
+ if (disp->mPosition == StylePositionProperty::Fixed) {
+ *aPlaceholderType |= PLACEHOLDER_FOR_FIXEDPOS;
+ return &mRealFixedList;
+ }
+ *aPlaceholderType |= PLACEHOLDER_FOR_ABSPOS;
+ return &mTopLayerAbsoluteList;
+ }
+ if (disp->mPosition == StylePositionProperty::Absolute) {
+ *aPlaceholderType = PLACEHOLDER_FOR_ABSPOS;
+ return &mAbsoluteList;
+ }
+ if (disp->mPosition == StylePositionProperty::Fixed) {
+ *aPlaceholderType = PLACEHOLDER_FOR_FIXEDPOS;
+ return mFixedList;
+ }
+ }
+ return nullptr;
+}
+
+void nsFrameConstructorState::ConstructBackdropFrameFor(nsIContent* aContent,
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->StyleDisplay()->mTopLayer == StyleTopLayer::Top);
+ nsContainerFrame* frame = do_QueryFrame(aFrame);
+ if (!frame) {
+ NS_WARNING("Cannot create backdrop frame for non-container frame");
+ return;
+ }
+
+ ComputedStyle* parentStyle = nsLayoutUtils::GetStyleFrame(aFrame)->Style();
+ RefPtr<ComputedStyle> style =
+ mPresShell->StyleSet()->ResolvePseudoElementStyle(
+ *aContent->AsElement(), PseudoStyleType::backdrop, nullptr,
+ parentStyle);
+ MOZ_ASSERT(style->StyleDisplay()->mTopLayer == StyleTopLayer::Top);
+ nsContainerFrame* parentFrame =
+ GetGeometricParent(*style->StyleDisplay(), nullptr);
+
+ nsBackdropFrame* backdropFrame =
+ new (mPresShell) nsBackdropFrame(style, mPresShell->GetPresContext());
+ backdropFrame->Init(aContent, parentFrame, nullptr);
+
+ nsFrameState placeholderType;
+ AbsoluteFrameList* frameList =
+ GetOutOfFlowFrameList(backdropFrame, true, true, &placeholderType);
+ MOZ_ASSERT(placeholderType & PLACEHOLDER_FOR_TOPLAYER);
+
+ nsIFrame* placeholder = nsCSSFrameConstructor::CreatePlaceholderFrameFor(
+ mPresShell, aContent, backdropFrame, frame, nullptr, placeholderType);
+ frame->SetInitialChildList(FrameChildListID::Backdrop,
+ nsFrameList(placeholder, placeholder));
+
+ frameList->AppendFrame(nullptr, backdropFrame);
+}
+
+void nsFrameConstructorState::AddChild(
+ nsIFrame* aNewFrame, nsFrameList& aFrameList, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, bool aCanBePositioned, bool aCanBeFloated,
+ bool aInsertAfter, nsIFrame* aInsertAfterFrame) {
+ MOZ_ASSERT(!aNewFrame->GetNextSibling(), "Shouldn't happen");
+
+ nsFrameState placeholderType;
+ AbsoluteFrameList* outOfFlowFrameList = GetOutOfFlowFrameList(
+ aNewFrame, aCanBePositioned, aCanBeFloated, &placeholderType);
+
+ // The comments in GetGeometricParent regarding root table frames
+ // all apply here, unfortunately. Thus, we need to check whether
+ // the returned frame items really has containing block.
+ nsFrameList* frameList;
+ if (outOfFlowFrameList && outOfFlowFrameList->mContainingBlock) {
+ MOZ_ASSERT(aNewFrame->GetParent() == outOfFlowFrameList->mContainingBlock,
+ "Parent of the frame is not the containing block?");
+ frameList = outOfFlowFrameList;
+ } else {
+ frameList = &aFrameList;
+ placeholderType = nsFrameState(0);
+ }
+
+ if (placeholderType) {
+ NS_ASSERTION(frameList != &aFrameList,
+ "Putting frame in-flow _and_ want a placeholder?");
+ nsIFrame* placeholderFrame =
+ nsCSSFrameConstructor::CreatePlaceholderFrameFor(
+ mPresShell, aContent, aNewFrame, aParentFrame, nullptr,
+ placeholderType);
+
+ placeholderFrame->AddStateBits(mAdditionalStateBits);
+ // Add the placeholder frame to the flow
+ aFrameList.AppendFrame(nullptr, placeholderFrame);
+
+ if (placeholderType & PLACEHOLDER_FOR_TOPLAYER) {
+ ConstructBackdropFrameFor(aContent, aNewFrame);
+ }
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(aNewFrame->GetParent() == aParentFrame,
+ "In-flow frame has wrong parent");
+ }
+#endif
+
+ if (aInsertAfter) {
+ frameList->InsertFrame(nullptr, aInsertAfterFrame, aNewFrame);
+ } else {
+ frameList->AppendFrame(nullptr, aNewFrame);
+ }
+}
+
+// Some of this function's callers recurse 1000 levels deep in crashtests. On
+// platforms where stack limits are low, we can't afford to incorporate this
+// function's `AutoTArray`s into its callers' stack frames, so disable inlining.
+MOZ_NEVER_INLINE void nsFrameConstructorState::ProcessFrameInsertions(
+ AbsoluteFrameList& aFrameList, FrameChildListID aChildListID) {
+ MOZ_ASSERT(&aFrameList == &mFloatedList || &aFrameList == &mAbsoluteList ||
+ &aFrameList == &mTopLayerAbsoluteList ||
+ &aFrameList == &mAncestorFixedList || &aFrameList == mFixedList ||
+ &aFrameList == &mRealFixedList);
+ MOZ_ASSERT_IF(&aFrameList == &mFloatedList,
+ aChildListID == FrameChildListID::Float);
+ MOZ_ASSERT_IF(&aFrameList == &mAbsoluteList || &aFrameList == mFixedList,
+ aChildListID == FrameChildListID::Absolute ||
+ aChildListID == FrameChildListID::Fixed);
+ MOZ_ASSERT_IF(&aFrameList == &mTopLayerAbsoluteList,
+ aChildListID == FrameChildListID::Absolute);
+ MOZ_ASSERT_IF(&aFrameList == mFixedList && &aFrameList != &mAbsoluteList,
+ aChildListID == FrameChildListID::Fixed);
+ MOZ_ASSERT_IF(&aFrameList == &mAncestorFixedList,
+ aChildListID == FrameChildListID::Fixed);
+ MOZ_ASSERT_IF(&aFrameList == &mRealFixedList,
+ aChildListID == FrameChildListID::Fixed);
+
+ if (aFrameList.IsEmpty()) {
+ return;
+ }
+
+ nsContainerFrame* containingBlock = aFrameList.mContainingBlock;
+
+ NS_ASSERTION(containingBlock, "Child list without containing block?");
+
+ if (aChildListID == FrameChildListID::Fixed) {
+ // Put this frame on the transformed-frame's abs-pos list instead, if
+ // it has abs-pos children instead of fixed-pos children.
+ aChildListID = containingBlock->GetAbsoluteListID();
+ }
+
+ // Insert the frames hanging out in aItems. We can use SetInitialChildList()
+ // if the containing block hasn't been reflowed yet (so NS_FRAME_FIRST_REFLOW
+ // is set) and doesn't have any frames in the aChildListID child list yet.
+ const nsFrameList& childList = containingBlock->GetChildList(aChildListID);
+ if (childList.IsEmpty() &&
+ containingBlock->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ // If we're injecting absolutely positioned frames, inject them on the
+ // absolute containing block
+ if (aChildListID == containingBlock->GetAbsoluteListID()) {
+ containingBlock->GetAbsoluteContainingBlock()->SetInitialChildList(
+ containingBlock, aChildListID, std::move(aFrameList));
+ } else {
+ containingBlock->SetInitialChildList(aChildListID, std::move(aFrameList));
+ }
+ } else if (aChildListID == FrameChildListID::Fixed ||
+ aChildListID == FrameChildListID::Absolute) {
+ // The order is not important for abs-pos/fixed-pos frame list, just
+ // append the frame items to the list directly.
+ mFrameConstructor->AppendFrames(containingBlock, aChildListID,
+ std::move(aFrameList));
+ } else {
+ // Note that whether the frame construction context is doing an append or
+ // not is not helpful here, since it could be appending to some frame in
+ // the middle of the document, which means we're not necessarily
+ // appending to the children of the containing block.
+ //
+ // We need to make sure the 'append to the end of document' case is fast.
+ // So first test the last child of the containing block
+ nsIFrame* lastChild = childList.LastChild();
+
+ // CompareTreePosition uses placeholder hierarchy for out of flow frames,
+ // so this will make out-of-flows respect the ordering of placeholders,
+ // which is great because it takes care of anonymous content.
+ nsIFrame* firstNewFrame = aFrameList.FirstChild();
+
+ // Cache the ancestor chain so that we can reuse it if needed.
+ AutoTArray<nsIFrame*, 20> firstNewFrameAncestors;
+ nsIFrame* notCommonAncestor = nullptr;
+ if (lastChild) {
+ notCommonAncestor = nsLayoutUtils::FillAncestors(
+ firstNewFrame, containingBlock, &firstNewFrameAncestors);
+ }
+
+ if (!lastChild || nsLayoutUtils::CompareTreePosition(
+ lastChild, firstNewFrame, firstNewFrameAncestors,
+ notCommonAncestor ? containingBlock : nullptr) < 0) {
+ // no lastChild, or lastChild comes before the new children, so just
+ // append
+ mFrameConstructor->AppendFrames(containingBlock, aChildListID,
+ std::move(aFrameList));
+ } else {
+ // Try the other children. First collect them to an array so that a
+ // reasonable fast binary search can be used to find the insertion point.
+ AutoTArray<nsIFrame*, 128> children;
+ for (nsIFrame* f = childList.FirstChild(); f != lastChild;
+ f = f->GetNextSibling()) {
+ children.AppendElement(f);
+ }
+
+ nsIFrame* insertionPoint = nullptr;
+ int32_t imin = 0;
+ int32_t max = children.Length();
+ while (max > imin) {
+ int32_t imid = imin + ((max - imin) / 2);
+ nsIFrame* f = children[imid];
+ int32_t compare = nsLayoutUtils::CompareTreePosition(
+ f, firstNewFrame, firstNewFrameAncestors,
+ notCommonAncestor ? containingBlock : nullptr);
+ if (compare > 0) {
+ // f is after the new frame.
+ max = imid;
+ insertionPoint = imid > 0 ? children[imid - 1] : nullptr;
+ } else if (compare < 0) {
+ // f is before the new frame.
+ imin = imid + 1;
+ insertionPoint = f;
+ } else {
+ // This is for the old behavior. Should be removed once it is
+ // guaranteed that CompareTreePosition can't return 0!
+ // See bug 928645.
+ NS_WARNING("Something odd happening???");
+ insertionPoint = nullptr;
+ for (uint32_t i = 0; i < children.Length(); ++i) {
+ nsIFrame* f = children[i];
+ if (nsLayoutUtils::CompareTreePosition(
+ f, firstNewFrame, firstNewFrameAncestors,
+ notCommonAncestor ? containingBlock : nullptr) > 0) {
+ break;
+ }
+ insertionPoint = f;
+ }
+ break;
+ }
+ }
+ mFrameConstructor->InsertFrames(containingBlock, aChildListID,
+ insertionPoint, std::move(aFrameList));
+ }
+ }
+
+ MOZ_ASSERT(aFrameList.IsEmpty(), "How did that happen?");
+}
+
+nsFrameConstructorSaveState::~nsFrameConstructorSaveState() {
+ // Restore the state
+ if (mList) {
+ MOZ_ASSERT(mState, "Can't have mList set without having a state!");
+ mState->ProcessFrameInsertions(*mList, mChildListID);
+
+ if (mList == &mState->mAbsoluteList) {
+ mState->mAbsoluteList = std::move(mSavedList);
+ mState->mFixedList = mSavedFixedList;
+ } else {
+ mState->mFloatedList = std::move(mSavedList);
+ }
+
+ MOZ_ASSERT(mSavedList.IsEmpty(),
+ "Frames in mSavedList should've moved back into mState!");
+ MOZ_ASSERT(!mList->LastChild() || !mList->LastChild()->GetNextSibling(),
+ "Something corrupted our list!");
+ }
+}
+
+/**
+ * Moves aFrameList from aOldParent to aNewParent. This updates the parent
+ * pointer of the frames in the list, and reparents their views as needed.
+ * nsIFrame::SetParent sets the NS_FRAME_HAS_VIEW bit on aNewParent and its
+ * ancestors as needed. Then it sets the list as the initial child list
+ * on aNewParent, unless aNewParent either already has kids or has been
+ * reflowed; in that case it appends the new frames. Note that this
+ * method differs from ReparentFrames in that it doesn't change the kids'
+ * style.
+ */
+// XXXbz Since this is only used for {ib} splits, could we just copy the view
+// bits from aOldParent to aNewParent and then use the
+// nsFrameList::ApplySetParent? That would still leave us doing two passes
+// over the list, of course; if we really wanted to we could factor out the
+// relevant part of ReparentFrameViewList, I suppose... Or just get rid of
+// views, which would make most of this function go away.
+static void MoveChildrenTo(nsIFrame* aOldParent, nsContainerFrame* aNewParent,
+ nsFrameList& aFrameList) {
+#ifdef DEBUG
+ bool sameGrandParent = aOldParent->GetParent() == aNewParent->GetParent();
+
+ if (aNewParent->HasView() || aOldParent->HasView() || !sameGrandParent) {
+ // Move the frames into the new view
+ nsContainerFrame::ReparentFrameViewList(aFrameList, aOldParent, aNewParent);
+ }
+#endif
+
+ aFrameList.ApplySetParent(aNewParent);
+
+ if (aNewParent->PrincipalChildList().IsEmpty() &&
+ aNewParent->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ aNewParent->SetInitialChildList(FrameChildListID::Principal,
+ std::move(aFrameList));
+ } else {
+ aNewParent->AppendFrames(FrameChildListID::Principal,
+ std::move(aFrameList));
+ }
+}
+
+static void EnsureAutoPageName(nsFrameConstructorState& aState,
+ const nsContainerFrame* const aFrame) {
+ // Check if we need to figure out our used page name.
+ // When building the entire document, this should only happen for the
+ // root, which will mean the loop will immediately end. Either way, this will
+ // only happen once for each time the frame constructor is run.
+ if (aState.mAutoPageNameValue) {
+ return;
+ }
+
+ for (const nsContainerFrame* frame = aFrame; frame;
+ frame = frame->GetParent()) {
+ if (const nsAtom* maybePageName = frame->GetStylePageName()) {
+ aState.mAutoPageNameValue = maybePageName;
+ return;
+ }
+ }
+ // Ensure that a root with `page: auto` gets an empty page name
+ // https://drafts.csswg.org/css-page-3/#using-named-pages
+ aState.mAutoPageNameValue = nsGkAtoms::_empty;
+}
+
+nsCSSFrameConstructor::AutoFrameConstructionPageName::
+ AutoFrameConstructionPageName(nsFrameConstructorState& aState,
+ nsIFrame* const aFrame)
+ : mState(aState), mNameToRestore(nullptr) {
+ if (!aState.mPresContext->IsPaginated()) {
+ MOZ_ASSERT(!aState.mAutoPageNameValue,
+ "Page name should not have been set");
+ return;
+ }
+#ifdef DEBUG
+ MOZ_ASSERT(!aFrame->mWasVisitedByAutoFrameConstructionPageName,
+ "Frame should only have been visited once");
+ aFrame->mWasVisitedByAutoFrameConstructionPageName = true;
+#endif
+
+ EnsureAutoPageName(aState, aFrame->GetParent());
+ mNameToRestore = aState.mAutoPageNameValue;
+
+ MOZ_ASSERT(mNameToRestore,
+ "Page name should have been found by EnsureAutoPageName");
+ if (const nsAtom* maybePageName = aFrame->GetStylePageName()) {
+ aState.mAutoPageNameValue = maybePageName;
+ }
+ aFrame->SetAutoPageValue(aState.mAutoPageNameValue);
+}
+
+nsCSSFrameConstructor::AutoFrameConstructionPageName::
+ ~AutoFrameConstructionPageName() {
+ // This isn't actually useful when not in paginated layout, but it's very
+ // likely cheaper to unconditionally write this pointer than to test for
+ // paginated layout and then branch on the result.
+ mState.mAutoPageNameValue = mNameToRestore;
+}
+
+//----------------------------------------------------------------------
+
+nsCSSFrameConstructor::nsCSSFrameConstructor(Document* aDocument,
+ PresShell* aPresShell)
+ : nsFrameManager(aPresShell),
+ mDocument(aDocument),
+ mFirstFreeFCItem(nullptr),
+ mFCItemsInUse(0),
+ mCurrentDepth(0),
+ mQuotesDirty(false),
+ mCountersDirty(false),
+ mAlwaysCreateFramesForIgnorableWhitespace(false) {
+#ifdef DEBUG
+ static bool gFirstTime = true;
+ if (gFirstTime) {
+ gFirstTime = false;
+ char* flags = PR_GetEnv("GECKO_FRAMECTOR_DEBUG_FLAGS");
+ if (flags) {
+ bool error = false;
+ for (;;) {
+ char* comma = strchr(flags, ',');
+ if (comma) *comma = '\0';
+
+ bool found = false;
+ FrameCtorDebugFlags* flag = gFlags;
+ FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
+ while (flag < limit) {
+ if (nsCRT::strcasecmp(flag->name, flags) == 0) {
+ *(flag->on) = true;
+ printf("nsCSSFrameConstructor: setting %s debug flag on\n",
+ flag->name);
+ found = true;
+ break;
+ }
+ ++flag;
+ }
+
+ if (!found) error = true;
+
+ if (!comma) break;
+
+ *comma = ',';
+ flags = comma + 1;
+ }
+
+ if (error) {
+ printf("Here are the available GECKO_FRAMECTOR_DEBUG_FLAGS:\n");
+ FrameCtorDebugFlags* flag = gFlags;
+ FrameCtorDebugFlags* limit = gFlags + NUM_DEBUG_FLAGS;
+ while (flag < limit) {
+ printf(" %s\n", flag->name);
+ ++flag;
+ }
+ printf(
+ "Note: GECKO_FRAMECTOR_DEBUG_FLAGS is a comma separated list of "
+ "flag\n");
+ printf("names (no whitespace)\n");
+ }
+ }
+ }
+#endif
+}
+
+void nsCSSFrameConstructor::NotifyDestroyingFrame(nsIFrame* aFrame) {
+ if (aFrame->StyleDisplay()->IsContainStyle()) {
+ mContainStyleScopeManager.DestroyScopesFor(aFrame);
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT) &&
+ mContainStyleScopeManager.DestroyQuoteNodesFor(aFrame)) {
+ QuotesDirty();
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE) &&
+ mContainStyleScopeManager.DestroyCounterNodesFor(aFrame)) {
+ // Technically we don't need to update anything if we destroyed only
+ // USE nodes. However, this is unlikely to happen in the real world
+ // since USE nodes generally go along with INCREMENT nodes.
+ CountersDirty();
+ }
+
+ RestyleManager()->NotifyDestroyingFrame(aFrame);
+}
+
+struct nsGenConInitializer {
+ UniquePtr<nsGenConNode> mNode;
+ nsGenConList* mList;
+ void (nsCSSFrameConstructor::*mDirtyAll)();
+
+ nsGenConInitializer(UniquePtr<nsGenConNode> aNode, nsGenConList* aList,
+ void (nsCSSFrameConstructor::*aDirtyAll)())
+ : mNode(std::move(aNode)), mList(aList), mDirtyAll(aDirtyAll) {}
+};
+
+already_AddRefed<nsIContent> nsCSSFrameConstructor::CreateGenConTextNode(
+ nsFrameConstructorState& aState, const nsAString& aString,
+ UniquePtr<nsGenConInitializer> aInitializer) {
+ RefPtr<nsTextNode> content = new (mDocument->NodeInfoManager())
+ nsTextNode(mDocument->NodeInfoManager());
+ content->SetText(aString, false);
+ if (aInitializer) {
+ aInitializer->mNode->mText = content;
+ content->SetProperty(nsGkAtoms::genConInitializerProperty,
+ aInitializer.release(),
+ nsINode::DeleteProperty<nsGenConInitializer>);
+ aState.mGeneratedContentWithInitializer.AppendElement(content);
+ }
+ return content.forget();
+}
+
+void nsCSSFrameConstructor::CreateGeneratedContent(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
+ const FunctionRef<void(nsIContent*)> aAddChild) {
+ using Type = StyleContentItem::Tag;
+ // Get the content value
+ const auto& item = aPseudoStyle.StyleContent()->ContentAt(aContentIndex);
+ const Type type = item.tag;
+
+ switch (type) {
+ case Type::Image: {
+ RefPtr c = GeneratedImageContent::Create(*mDocument, aContentIndex);
+ aAddChild(c);
+ return;
+ }
+
+ case Type::String: {
+ const auto string = item.AsString().AsString();
+ if (string.IsEmpty()) {
+ return;
+ }
+ RefPtr text =
+ CreateGenConTextNode(aState, NS_ConvertUTF8toUTF16(string), nullptr);
+ aAddChild(text);
+ return;
+ }
+
+ case Type::Attr: {
+ const auto& attr = item.AsAttr();
+ RefPtr<nsAtom> attrName = attr.attribute.AsAtom();
+ int32_t attrNameSpace = kNameSpaceID_None;
+ RefPtr<nsAtom> ns = attr.namespace_url.AsAtom();
+ if (!ns->IsEmpty()) {
+ nsresult rv = nsNameSpaceManager::GetInstance()->RegisterNameSpace(
+ ns.forget(), attrNameSpace);
+ NS_ENSURE_SUCCESS_VOID(rv);
+ }
+
+ if (mDocument->IsHTMLDocument() && aOriginatingElement.IsHTMLElement()) {
+ ToLowerCaseASCII(attrName);
+ }
+
+ RefPtr<nsAtom> fallback = attr.fallback.AsAtom();
+
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(), attrNameSpace,
+ attrName, fallback, getter_AddRefs(content));
+ aAddChild(content);
+ return;
+ }
+
+ case Type::Counter:
+ case Type::Counters: {
+ RefPtr<nsAtom> name;
+ CounterStylePtr ptr;
+ nsString separator;
+ if (type == Type::Counter) {
+ auto& counter = item.AsCounter();
+ name = counter._0.AsAtom();
+ ptr = CounterStylePtr::FromStyle(counter._1);
+ } else {
+ auto& counters = item.AsCounters();
+ name = counters._0.AsAtom();
+ CopyUTF8toUTF16(counters._1.AsString(), separator);
+ ptr = CounterStylePtr::FromStyle(counters._2);
+ }
+
+ auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList(
+ aOriginatingElement, name);
+ auto node = MakeUnique<nsCounterUseNode>(
+ std::move(ptr), std::move(separator), aContentIndex,
+ /* aAllCounters = */ type == Type::Counters);
+
+ auto initializer = MakeUnique<nsGenConInitializer>(
+ std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
+ RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer));
+ aAddChild(c);
+ return;
+ }
+ case Type::OpenQuote:
+ case Type::CloseQuote:
+ case Type::NoOpenQuote:
+ case Type::NoCloseQuote: {
+ auto node = MakeUnique<nsQuoteNode>(type, aContentIndex);
+ auto* quoteList =
+ mContainStyleScopeManager.QuoteListFor(aOriginatingElement);
+ auto initializer = MakeUnique<nsGenConInitializer>(
+ std::move(node), quoteList, &nsCSSFrameConstructor::QuotesDirty);
+ RefPtr c = CreateGenConTextNode(aState, u""_ns, std::move(initializer));
+ aAddChild(c);
+ return;
+ }
+
+ case Type::MozLabelContent: {
+ nsAutoString accesskey;
+ if (!aOriginatingElement.GetAttr(nsGkAtoms::accesskey, accesskey) ||
+ accesskey.IsEmpty() || !LookAndFeel::GetMenuAccessKey()) {
+ // Easy path: just return a regular value attribute content.
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None,
+ nsGkAtoms::value, nsGkAtoms::_empty,
+ getter_AddRefs(content));
+ aAddChild(content);
+ return;
+ }
+
+ nsAutoString value;
+ aOriginatingElement.GetAttr(nsGkAtoms::value, value);
+
+ auto AppendAccessKeyLabel = [&] {
+ // Always append accesskey text in uppercase, see bug 1806167.
+ ToUpperCase(accesskey);
+ nsAutoString accessKeyLabel = u"("_ns + accesskey + u")"_ns;
+ if (!StringEndsWith(value, accessKeyLabel)) {
+ if (InsertSeparatorBeforeAccessKey() && !value.IsEmpty() &&
+ !NS_IS_SPACE(value.Last())) {
+ value.Append(' ');
+ }
+ value.Append(accessKeyLabel);
+ }
+ };
+ if (AlwaysAppendAccessKey()) {
+ AppendAccessKeyLabel();
+ RefPtr c = CreateGenConTextNode(aState, value, nullptr);
+ aAddChild(c);
+ return;
+ }
+
+ const auto accessKeyStart = [&]() -> Maybe<size_t> {
+ nsAString::const_iterator start, end;
+ value.BeginReading(start);
+ value.EndReading(end);
+
+ const auto originalStart = start;
+ // not appending access key - do case-sensitive search
+ // first
+ bool found = true;
+ if (!FindInReadable(accesskey, start, end)) {
+ start = originalStart;
+ // didn't find it - perform a case-insensitive search
+ found = FindInReadable(accesskey, start, end,
+ nsCaseInsensitiveStringComparator);
+ }
+ if (!found) {
+ return Nothing();
+ }
+ return Some(Distance(originalStart, start));
+ }();
+
+ if (accessKeyStart.isNothing()) {
+ AppendAccessKeyLabel();
+ RefPtr c = CreateGenConTextNode(aState, value, nullptr);
+ aAddChild(c);
+ return;
+ }
+
+ if (*accessKeyStart != 0) {
+ RefPtr beginning = CreateGenConTextNode(
+ aState, Substring(value, 0, *accessKeyStart), nullptr);
+ aAddChild(beginning);
+ }
+
+ {
+ RefPtr accessKeyText = CreateGenConTextNode(
+ aState, Substring(value, *accessKeyStart, accesskey.Length()),
+ nullptr);
+ RefPtr<nsIContent> underline =
+ mDocument->CreateHTMLElement(nsGkAtoms::u);
+ underline->AppendChildTo(accessKeyText, /* aNotify = */ false,
+ IgnoreErrors());
+ aAddChild(underline);
+ }
+
+ size_t accessKeyEnd = *accessKeyStart + accesskey.Length();
+ if (accessKeyEnd != value.Length()) {
+ RefPtr valueEnd = CreateGenConTextNode(
+ aState, Substring(value, *accessKeyStart + accesskey.Length()),
+ nullptr);
+ aAddChild(valueEnd);
+ }
+ break;
+ }
+ case Type::MozAltContent: {
+ // Use the "alt" attribute; if that fails and the node is an HTML
+ // <input>, try the value attribute and then fall back to some default
+ // localized text we have.
+ // XXX what if the 'alt' attribute is added later, how will we
+ // detect that and do the right thing here?
+ if (aOriginatingElement.HasAttr(nsGkAtoms::alt)) {
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(), kNameSpaceID_None,
+ nsGkAtoms::alt, nsGkAtoms::_empty,
+ getter_AddRefs(content));
+ aAddChild(content);
+ return;
+ }
+
+ if (aOriginatingElement.IsHTMLElement(nsGkAtoms::input)) {
+ if (aOriginatingElement.HasAttr(nsGkAtoms::value)) {
+ nsCOMPtr<nsIContent> content;
+ NS_NewAttributeContent(mDocument->NodeInfoManager(),
+ kNameSpaceID_None, nsGkAtoms::value,
+ nsGkAtoms::_empty, getter_AddRefs(content));
+ aAddChild(content);
+ return;
+ }
+
+ nsAutoString temp;
+ nsContentUtils::GetMaybeLocalizedString(
+ nsContentUtils::eFORMS_PROPERTIES, "Submit", mDocument, temp);
+ RefPtr c = CreateGenConTextNode(aState, temp, nullptr);
+ aAddChild(c);
+ return;
+ }
+ break;
+ }
+ }
+
+ return;
+}
+
+void nsCSSFrameConstructor::CreateGeneratedContentFromListStyle(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ const ComputedStyle& aPseudoStyle,
+ const FunctionRef<void(nsIContent*)> aAddChild) {
+ const nsStyleList* styleList = aPseudoStyle.StyleList();
+ if (!styleList->mListStyleImage.IsNone()) {
+ RefPtr<nsIContent> child =
+ GeneratedImageContent::CreateForListStyleImage(*mDocument);
+ aAddChild(child);
+ child = CreateGenConTextNode(aState, u" "_ns, nullptr);
+ aAddChild(child);
+ return;
+ }
+ CreateGeneratedContentFromListStyleType(aState, aOriginatingElement,
+ aPseudoStyle, aAddChild);
+}
+
+void nsCSSFrameConstructor::CreateGeneratedContentFromListStyleType(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ const ComputedStyle& aPseudoStyle,
+ const FunctionRef<void(nsIContent*)> aAddChild) {
+ const nsStyleList* styleList = aPseudoStyle.StyleList();
+ CounterStyle* counterStyle =
+ mPresShell->GetPresContext()->CounterStyleManager()->ResolveCounterStyle(
+ styleList->mCounterStyle);
+ bool needUseNode = false;
+ switch (counterStyle->GetStyle()) {
+ case ListStyle::None:
+ return;
+ case ListStyle::Disc:
+ case ListStyle::Circle:
+ case ListStyle::Square:
+ case ListStyle::DisclosureClosed:
+ case ListStyle::DisclosureOpen:
+ break;
+ default:
+ const auto* anonStyle = counterStyle->AsAnonymous();
+ if (!anonStyle || !anonStyle->IsSingleString()) {
+ needUseNode = true;
+ }
+ }
+
+ auto node = MakeUnique<nsCounterUseNode>(nsCounterUseNode::ForLegacyBullet,
+ styleList->mCounterStyle);
+ if (!needUseNode) {
+ nsAutoString text;
+ node->GetText(WritingMode(&aPseudoStyle), counterStyle, text);
+ // Note that we're done with 'node' in this case. It's not inserted into
+ // any list so it's deleted when we return.
+ RefPtr<nsIContent> child = CreateGenConTextNode(aState, text, nullptr);
+ aAddChild(child);
+ return;
+ }
+
+ auto* counterList = mContainStyleScopeManager.GetOrCreateCounterList(
+ aOriginatingElement, nsGkAtoms::list_item);
+ auto initializer = MakeUnique<nsGenConInitializer>(
+ std::move(node), counterList, &nsCSSFrameConstructor::CountersDirty);
+ RefPtr<nsIContent> child =
+ CreateGenConTextNode(aState, EmptyString(), std::move(initializer));
+ aAddChild(child);
+}
+
+// Frames for these may not be leaves in the proper sense, but we still don't
+// want to expose generated content on them. For the purposes of the page they
+// should be leaves.
+static bool HasUAWidget(const Element& aOriginatingElement) {
+ const ShadowRoot* sr = aOriginatingElement.GetShadowRoot();
+ return sr && sr->IsUAWidget();
+}
+
+/*
+ * aParentFrame - the frame that should be the parent of the generated
+ * content. This is the frame for the corresponding content node,
+ * which must not be a leaf frame.
+ *
+ * Any items created are added to aItems.
+ *
+ * We create an XML element (tag _moz_generated_content_before/after/marker)
+ * representing the pseudoelement. We create a DOM node for each 'content'
+ * item and make those nodes the children of the XML element. Then we create
+ * a frame subtree for the XML element as if it were a regular child of
+ * aParentFrame/aParentContent, giving the XML element the ::before, ::after
+ * or ::marker style.
+ */
+void nsCSSFrameConstructor::CreateGeneratedContentItem(
+ nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
+ Element& aOriginatingElement, ComputedStyle& aStyle,
+ PseudoStyleType aPseudoElement, FrameConstructionItemList& aItems,
+ ItemFlags aExtraFlags) {
+ MOZ_ASSERT(aPseudoElement == PseudoStyleType::before ||
+ aPseudoElement == PseudoStyleType::after ||
+ aPseudoElement == PseudoStyleType::marker,
+ "unexpected aPseudoElement");
+
+ if (HasUAWidget(aOriginatingElement) &&
+ !aOriginatingElement.IsHTMLElement(nsGkAtoms::details)) {
+ return;
+ }
+
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+
+ // Probe for the existence of the pseudo-element.
+ // |ProbePseudoElementStyle| checks the relevant properties for the pseudo.
+ // It only returns a non-null value if the pseudo should exist.
+ RefPtr<ComputedStyle> pseudoStyle = styleSet->ProbePseudoElementStyle(
+ aOriginatingElement, aPseudoElement, nullptr, &aStyle);
+ if (!pseudoStyle) {
+ return;
+ }
+
+ nsAtom* elemName = nullptr;
+ nsAtom* property = nullptr;
+ switch (aPseudoElement) {
+ case PseudoStyleType::before:
+ elemName = nsGkAtoms::mozgeneratedcontentbefore;
+ property = nsGkAtoms::beforePseudoProperty;
+ break;
+ case PseudoStyleType::after:
+ elemName = nsGkAtoms::mozgeneratedcontentafter;
+ property = nsGkAtoms::afterPseudoProperty;
+ break;
+ case PseudoStyleType::marker:
+ // We want to get a marker style even if we match no rules, but we still
+ // want to check the result of GeneratedContentPseudoExists.
+ elemName = nsGkAtoms::mozgeneratedcontentmarker;
+ property = nsGkAtoms::markerPseudoProperty;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unexpected aPseudoElement");
+ }
+
+ RefPtr<NodeInfo> nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(
+ elemName, nullptr, kNameSpaceID_None, nsINode::ELEMENT_NODE);
+ RefPtr<Element> container;
+ nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // Cleared when the pseudo is unbound from the tree, so no need to store a
+ // strong reference, nor a destructor.
+ aOriginatingElement.SetProperty(property, container.get());
+
+ container->SetIsNativeAnonymousRoot();
+ container->SetPseudoElementType(aPseudoElement);
+
+ BindContext context(aOriginatingElement, BindContext::ForNativeAnonymous);
+ rv = container->BindToTree(context, aOriginatingElement);
+ if (NS_FAILED(rv)) {
+ container->UnbindFromTree();
+ return;
+ }
+
+ if (mDocument->DevToolsAnonymousAndShadowEventsEnabled()) {
+ container->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ false);
+ }
+
+ // Servo has already eagerly computed the style for the container, so we can
+ // just stick the style on the element and avoid an additional traversal.
+ //
+ // We don't do this for pseudos that may trigger animations or transitions,
+ // since those need to be kicked off by the traversal machinery.
+ //
+ // Note that when a pseudo-element animates, we flag the originating element,
+ // so we check that flag, but we could also a more expensive (but exhaustive)
+ // check using EffectSet::GetEffectSet, for example.
+ if (!Servo_ComputedValues_SpecifiesAnimationsOrTransitions(pseudoStyle) &&
+ !aOriginatingElement.MayHaveAnimations()) {
+ Servo_SetExplicitStyle(container, pseudoStyle);
+ } else {
+ // If animations are involved, we avoid the SetExplicitStyle optimization
+ // above. We need to grab style with animations from the pseudo element and
+ // replace old one.
+ mPresShell->StyleSet()->StyleNewSubtree(container);
+ pseudoStyle = ServoStyleSet::ResolveServoStyle(*container);
+ }
+
+ auto AppendChild = [&container, this](nsIContent* aChild) {
+ // We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
+ // here; it would get set under AppendChildTo. But AppendChildTo might
+ // think that we're going from not being anonymous to being anonymous and
+ // do some extra work; setting the flag here avoids that.
+ aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ container->AppendChildTo(aChild, false, IgnoreErrors());
+ if (auto* childElement = Element::FromNode(aChild)) {
+ // If we created any children elements, Servo needs to traverse them, but
+ // the root is already set up.
+ mPresShell->StyleSet()->StyleNewSubtree(childElement);
+ }
+ };
+ const uint32_t contentCount = pseudoStyle->StyleContent()->ContentCount();
+ for (uint32_t contentIndex = 0; contentIndex < contentCount; contentIndex++) {
+ CreateGeneratedContent(aState, aOriginatingElement, *pseudoStyle,
+ contentIndex, AppendChild);
+ }
+ // If a ::marker has no 'content' then generate it from its 'list-style-*'.
+ if (contentCount == 0 && aPseudoElement == PseudoStyleType::marker) {
+ CreateGeneratedContentFromListStyle(aState, aOriginatingElement,
+ *pseudoStyle, AppendChild);
+ }
+ auto flags = ItemFlags{ItemFlag::IsGeneratedContent} + aExtraFlags;
+ AddFrameConstructionItemsInternal(aState, container, aParentFrame, true,
+ pseudoStyle, flags, aItems);
+}
+
+/****************************************************
+ ** BEGIN TABLE SECTION
+ ****************************************************/
+
+// The term pseudo frame is being used instead of anonymous frame, since
+// anonymous frame has been used elsewhere to refer to frames that have
+// generated content
+
+// Return whether the given frame is a table pseudo-frame. Note that
+// cell-content and table-outer frames have pseudo-types, but are always
+// created, even for non-anonymous cells and tables respectively. So for those
+// we have to examine the cell or table frame to see whether it's a pseudo
+// frame. In particular, a lone table caption will have a table wrapper as its
+// parent, but will also trigger construction of an empty inner table, which
+// will be the one we can examine to see whether the wrapper was a pseudo-frame.
+static bool IsTablePseudo(nsIFrame* aFrame) {
+ auto pseudoType = aFrame->Style()->GetPseudoType();
+ return pseudoType != PseudoStyleType::NotPseudo &&
+ (pseudoType == PseudoStyleType::table ||
+ pseudoType == PseudoStyleType::inlineTable ||
+ pseudoType == PseudoStyleType::tableColGroup ||
+ pseudoType == PseudoStyleType::tableRowGroup ||
+ pseudoType == PseudoStyleType::tableRow ||
+ pseudoType == PseudoStyleType::tableCell ||
+ (pseudoType == PseudoStyleType::cellContent &&
+ aFrame->GetParent()->Style()->GetPseudoType() ==
+ PseudoStyleType::tableCell) ||
+ (pseudoType == PseudoStyleType::tableWrapper &&
+ (aFrame->PrincipalChildList()
+ .FirstChild()
+ ->Style()
+ ->GetPseudoType() == PseudoStyleType::table ||
+ aFrame->PrincipalChildList()
+ .FirstChild()
+ ->Style()
+ ->GetPseudoType() == PseudoStyleType::inlineTable)));
+}
+
+static bool IsRubyPseudo(nsIFrame* aFrame) {
+ return RubyUtils::IsRubyPseudo(aFrame->Style()->GetPseudoType());
+}
+
+static bool IsTableOrRubyPseudo(nsIFrame* aFrame) {
+ return IsTablePseudo(aFrame) || IsRubyPseudo(aFrame);
+}
+
+/* static */
+nsCSSFrameConstructor::ParentType nsCSSFrameConstructor::GetParentType(
+ LayoutFrameType aFrameType) {
+ if (aFrameType == LayoutFrameType::Table) {
+ return eTypeTable;
+ }
+ if (aFrameType == LayoutFrameType::TableRowGroup) {
+ return eTypeRowGroup;
+ }
+ if (aFrameType == LayoutFrameType::TableRow) {
+ return eTypeRow;
+ }
+ if (aFrameType == LayoutFrameType::TableColGroup) {
+ return eTypeColGroup;
+ }
+ if (aFrameType == LayoutFrameType::RubyBaseContainer) {
+ return eTypeRubyBaseContainer;
+ }
+ if (aFrameType == LayoutFrameType::RubyTextContainer) {
+ return eTypeRubyTextContainer;
+ }
+ if (aFrameType == LayoutFrameType::Ruby) {
+ return eTypeRuby;
+ }
+
+ return eTypeBlock;
+}
+
+// Pull all the captions present in aItems out into aCaptions.
+static void PullOutCaptionFrames(nsFrameList& aList, nsFrameList& aCaptions) {
+ nsIFrame* child = aList.FirstChild();
+ while (child) {
+ nsIFrame* nextSibling = child->GetNextSibling();
+ if (child->StyleDisplay()->mDisplay == StyleDisplay::TableCaption) {
+ aList.RemoveFrame(child);
+ aCaptions.AppendFrame(nullptr, child);
+ }
+ child = nextSibling;
+ }
+}
+
+// Construct the outer, inner table frames and the children frames for the
+// table.
+// XXX Page break frames for pseudo table frames are not constructed to avoid
+// the risk associated with revising the pseudo frame mechanism. The long term
+// solution of having frames handle page-break-before/after will solve the
+// problem.
+nsIFrame* nsCSSFrameConstructor::ConstructTable(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::Table ||
+ aDisplay->mDisplay == StyleDisplay::InlineTable,
+ "Unexpected call");
+
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+ const bool isMathMLContent = content->IsMathMLElement();
+
+ // create the pseudo SC for the table wrapper as a child of the inner SC
+ RefPtr<ComputedStyle> outerComputedStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::tableWrapper, computedStyle);
+
+ // Create the table wrapper frame which holds the caption and inner table
+ // frame
+ nsContainerFrame* newFrame;
+ if (isMathMLContent)
+ newFrame = NS_NewMathMLmtableOuterFrame(mPresShell, outerComputedStyle);
+ else
+ newFrame = NS_NewTableWrapperFrame(mPresShell, outerComputedStyle);
+
+ nsContainerFrame* geometricParent = aState.GetGeometricParent(
+ *outerComputedStyle->StyleDisplay(), aParentFrame);
+
+ // Init the table wrapper frame
+ InitAndRestoreFrame(aState, content, geometricParent, newFrame);
+
+ // Create the inner table frame
+ nsContainerFrame* innerFrame;
+ if (isMathMLContent)
+ innerFrame = NS_NewMathMLmtableFrame(mPresShell, computedStyle);
+ else
+ innerFrame = NS_NewTableFrame(mPresShell, computedStyle);
+
+ InitAndRestoreFrame(aState, content, newFrame, innerFrame);
+ innerFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Put the newly created frames into the right child list
+ SetInitialSingleChild(newFrame, innerFrame);
+
+ aState.AddChild(newFrame, aFrameList, content, aParentFrame);
+
+ if (!mRootElementFrame) {
+ mRootElementFrame = newFrame;
+ }
+
+ nsFrameList childList;
+
+ // Process children
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ // Mark the table frame as an absolute container if needed
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (newFrame->IsAbsPosContainingBlock()) {
+ aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(innerFrame, floatSaveState);
+
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(
+ aState, aItem.mChildItems, innerFrame,
+ aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
+ } else {
+ ProcessChildren(aState, content, computedStyle, innerFrame, true, childList,
+ false);
+ }
+
+ nsFrameList captionList;
+ PullOutCaptionFrames(childList, captionList);
+
+ // Set the inner table frame's initial primary list
+ innerFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+
+ // Set the table wrapper frame's secondary childlist lists
+ if (captionList.NotEmpty()) {
+ captionList.ApplySetParent(newFrame);
+ newFrame->SetInitialChildList(FrameChildListID::Caption,
+ std::move(captionList));
+ }
+
+ return newFrame;
+}
+
+static void MakeTablePartAbsoluteContainingBlock(
+ nsFrameConstructorState& aState, nsFrameConstructorSaveState& aAbsSaveState,
+ nsContainerFrame* aFrame) {
+ // If we're positioned, then we need to become an absolute containing block
+ // for any absolutely positioned children.
+ aFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (aFrame->IsAbsPosContainingBlock()) {
+ aState.PushAbsoluteContainingBlock(aFrame, aFrame, aAbsSaveState);
+ }
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructTableRowOrRowGroup(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableRow ||
+ aDisplay->mDisplay == StyleDisplay::TableRowGroup ||
+ aDisplay->mDisplay == StyleDisplay::TableFooterGroup ||
+ aDisplay->mDisplay == StyleDisplay::TableHeaderGroup,
+ "Not a row or row group");
+ MOZ_ASSERT(aItem.mComputedStyle->StyleDisplay() == aDisplay,
+ "Display style doesn't match style");
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsContainerFrame* newFrame;
+ if (aDisplay->mDisplay == StyleDisplay::TableRow) {
+ if (content->IsMathMLElement())
+ newFrame = NS_NewMathMLmtrFrame(mPresShell, computedStyle);
+ else
+ newFrame = NS_NewTableRowFrame(mPresShell, computedStyle);
+ } else {
+ newFrame = NS_NewTableRowGroupFrame(mPresShell, computedStyle);
+ }
+
+ InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlock(aState, absoluteSaveState, newFrame);
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(newFrame, floatSaveState);
+
+ nsFrameList childList;
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(
+ aState, aItem.mChildItems, newFrame,
+ aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
+ } else {
+ ProcessChildren(aState, content, computedStyle, newFrame, true, childList,
+ false);
+ }
+
+ newFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ aFrameList.AppendFrame(nullptr, newFrame);
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructTableCol(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList) {
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsTableColFrame* colFrame = NS_NewTableColFrame(mPresShell, computedStyle);
+ InitAndRestoreFrame(aState, content, aParentFrame, colFrame);
+
+ NS_ASSERTION(colFrame->Style() == computedStyle, "Unexpected style");
+
+ aFrameList.AppendFrame(nullptr, colFrame);
+
+ // construct additional col frames if the col frame has a span > 1
+ int32_t span = colFrame->GetSpan();
+ for (int32_t spanX = 1; spanX < span; spanX++) {
+ nsTableColFrame* newCol = NS_NewTableColFrame(mPresShell, computedStyle);
+ InitAndRestoreFrame(aState, content, aParentFrame, newCol, false);
+ aFrameList.LastChild()->SetNextContinuation(newCol);
+ newCol->SetPrevContinuation(aFrameList.LastChild());
+ aFrameList.AppendFrame(nullptr, newCol);
+ newCol->SetColType(eColAnonymousCol);
+ }
+
+ return colFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructTableCell(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ MOZ_ASSERT(aDisplay->mDisplay == StyleDisplay::TableCell, "Unexpected call");
+
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+ const bool isMathMLContent = content->IsMathMLElement();
+
+ nsTableFrame* tableFrame =
+ static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
+ nsContainerFrame* cellFrame;
+ // <mtable> is border separate in mathml.css and the MathML code doesn't
+ // implement border collapse. For those users who style <mtable> with border
+ // collapse, give them the default non-MathML table frames that understand
+ // border collapse. This won't break us because MathML table frames are all
+ // subclasses of the default table code, and so we can freely mix <mtable>
+ // with <mtr> or <tr>, <mtd> or <td>. What will happen is just that non-MathML
+ // frames won't understand MathML attributes and will therefore miss the
+ // special handling that the MathML code does.
+ if (isMathMLContent && !tableFrame->IsBorderCollapse()) {
+ cellFrame = NS_NewMathMLmtdFrame(mPresShell, computedStyle, tableFrame);
+ } else {
+ // Warning: If you change this and add a wrapper frame around table cell
+ // frames, make sure Bug 368554 doesn't regress!
+ // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
+ cellFrame = NS_NewTableCellFrame(mPresShell, computedStyle, tableFrame);
+ }
+
+ // Initialize the table cell frame
+ InitAndRestoreFrame(aState, content, aParentFrame, cellFrame);
+ cellFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Resolve pseudo style and initialize the body cell frame
+ RefPtr<ComputedStyle> innerPseudoStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::cellContent, computedStyle);
+
+ nsContainerFrame* cellInnerFrame;
+ nsContainerFrame* scrollFrame = nullptr;
+ bool isScrollable = false;
+ // Create a block frame that will format the cell's content
+ if (isMathMLContent) {
+ cellInnerFrame = NS_NewMathMLmtdInnerFrame(mPresShell, innerPseudoStyle);
+ } else {
+ isScrollable = innerPseudoStyle->StyleDisplay()->IsScrollableOverflow() &&
+ !aState.mPresContext->IsPaginated() &&
+ StaticPrefs::layout_tables_scrollable_cells();
+ if (isScrollable) {
+ innerPseudoStyle = BeginBuildingScrollFrame(
+ aState, content, innerPseudoStyle, cellFrame,
+ PseudoStyleType::scrolledContent, false, scrollFrame);
+ }
+ cellInnerFrame = NS_NewBlockFormattingContext(mPresShell, innerPseudoStyle);
+ }
+ auto* parent = scrollFrame ? scrollFrame : cellFrame;
+ InitAndRestoreFrame(aState, content, parent, cellInnerFrame);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlock(aState, absoluteSaveState, cellFrame);
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(cellInnerFrame, floatSaveState);
+
+ nsFrameList childList;
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ AutoFrameConstructionPageName pageNameTracker(aState, cellInnerFrame);
+ ConstructFramesFromItemList(
+ aState, aItem.mChildItems, cellInnerFrame,
+ aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
+ } else {
+ // Process the child content
+ ProcessChildren(aState, content, computedStyle, cellInnerFrame, true,
+ childList, !isMathMLContent);
+ }
+
+ cellInnerFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+
+ if (isScrollable) {
+ FinishBuildingScrollFrame(scrollFrame, cellInnerFrame);
+ }
+ SetInitialSingleChild(cellFrame, scrollFrame ? scrollFrame : cellInnerFrame);
+ aFrameList.AppendFrame(nullptr, cellFrame);
+ return cellFrame;
+}
+
+static inline bool NeedFrameFor(const nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aChildContent) {
+ // XXX the GetContent() != aChildContent check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ MOZ_ASSERT(
+ !aChildContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
+ aChildContent->GetPrimaryFrame()->GetContent() != aChildContent,
+ "Why did we get called?");
+
+ // don't create a whitespace frame if aParentFrame doesn't want it.
+ // always create frames for children in generated content. counter(),
+ // quotes, and attr() content can easily change dynamically and we don't
+ // want to be reconstructing frames. It's not even clear that these
+ // should be considered ignorable just because they evaluate to
+ // whitespace.
+
+ // We could handle all this in CreateNeededPseudoContainers or some other
+ // place after we build our frame construction items, but that would involve
+ // creating frame construction items for whitespace kids that ignores
+ // white-space, where we know we'll be dropping them all anyway, and involve
+ // an extra walk down the frame construction item list.
+ auto excludesIgnorableWhitespace = [](nsIFrame* aParentFrame) {
+ return aParentFrame->IsMathMLFrame();
+ };
+ if (!aParentFrame || !excludesIgnorableWhitespace(aParentFrame) ||
+ aParentFrame->IsGeneratedContentFrame() || !aChildContent->IsText()) {
+ return true;
+ }
+
+ aChildContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
+ NS_REFRAME_IF_WHITESPACE);
+ return !aChildContent->TextIsOnlyWhitespace();
+}
+
+/***********************************************
+ * END TABLE SECTION
+ ***********************************************/
+
+nsIFrame* nsCSSFrameConstructor::ConstructDocElementFrame(
+ Element* aDocElement) {
+ MOZ_ASSERT(GetRootFrame(),
+ "No viewport? Someone forgot to call ConstructRootFrame!");
+ MOZ_ASSERT(!mDocElementContainingBlock,
+ "Shouldn't have a doc element containing block here");
+
+ // Resolve a new style for the viewport since it may be affected by a new root
+ // element style (e.g. a propagated 'direction').
+ //
+ // @see ComputedStyle::ApplyStyleFixups
+ {
+ RefPtr<ComputedStyle> sc =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::viewport, nullptr);
+ GetRootFrame()->SetComputedStyleWithoutNotification(sc);
+ }
+
+ // Ensure the document element is styled at this point.
+ if (!aDocElement->HasServoData()) {
+ mPresShell->StyleSet()->StyleNewSubtree(aDocElement);
+ }
+ aDocElement->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+
+ // Make sure to call UpdateViewportScrollStylesOverride before
+ // SetUpDocElementContainingBlock, since it sets up our scrollbar state
+ // properly.
+ DebugOnly<nsIContent*> propagatedScrollFrom;
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ propagatedScrollFrom = presContext->UpdateViewportScrollStylesOverride();
+ }
+
+ SetUpDocElementContainingBlock(aDocElement);
+
+ // This has the side-effect of getting `mFrameTreeState` from our docshell.
+ //
+ // FIXME(emilio): There may be a more sensible time to do this.
+ if (!mFrameTreeState) {
+ mPresShell->CaptureHistoryState(getter_AddRefs(mFrameTreeState));
+ }
+
+ NS_ASSERTION(mDocElementContainingBlock, "Should have parent by now");
+ nsFrameConstructorState state(
+ mPresShell,
+ GetAbsoluteContainingBlock(mDocElementContainingBlock, FIXED_POS),
+ nullptr, nullptr, do_AddRef(mFrameTreeState));
+
+ RefPtr<ComputedStyle> computedStyle =
+ ServoStyleSet::ResolveServoStyle(*aDocElement);
+
+ const nsStyleDisplay* display = computedStyle->StyleDisplay();
+
+ // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
+
+ NS_ASSERTION(!display->IsScrollableOverflow() ||
+ state.mPresContext->IsPaginated() ||
+ propagatedScrollFrom == aDocElement,
+ "Scrollbars should have been propagated to the viewport");
+
+ if (MOZ_UNLIKELY(display->mDisplay == StyleDisplay::None)) {
+ return nullptr;
+ }
+
+ // This implements "The Principal Writing Mode".
+ // https://drafts.csswg.org/css-writing-modes-3/#principal-flow
+ //
+ // If there's a <body> element in an HTML document, its writing-mode,
+ // direction, and text-orientation override the root element's used value.
+ //
+ // We need to copy <body>'s WritingMode to mDocElementContainingBlock before
+ // construct mRootElementFrame so that anonymous internal frames such as
+ // <html> with table style can copy their parent frame's mWritingMode in
+ // nsIFrame::Init().
+ MOZ_ASSERT(!mRootElementFrame,
+ "We need to copy <body>'s principal writing-mode before "
+ "constructing mRootElementFrame.");
+
+ const WritingMode propagatedWM = [&] {
+ const WritingMode rootWM(computedStyle);
+ if (computedStyle->StyleDisplay()->IsContainAny()) {
+ return rootWM;
+ }
+ Element* body = mDocument->GetBodyElement();
+ if (!body) {
+ return rootWM;
+ }
+ RefPtr<ComputedStyle> bodyStyle = ResolveComputedStyle(body);
+ if (bodyStyle->StyleDisplay()->IsContainAny()) {
+ return rootWM;
+ }
+ const WritingMode bodyWM(bodyStyle);
+ if (bodyWM != rootWM) {
+ nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "Layout"_ns,
+ mDocument,
+ nsContentUtils::eLAYOUT_PROPERTIES,
+ "PrincipalWritingModePropagationWarning");
+ }
+ return bodyWM;
+ }();
+
+ mDocElementContainingBlock->PropagateWritingModeToSelfAndAncestors(
+ propagatedWM);
+
+ // Push the absolute containing block now so we can absolutely position the
+ // root element
+ nsFrameConstructorSaveState canvasCbSaveState;
+ mCanvasFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+
+ state.PushAbsoluteContainingBlock(mCanvasFrame, mCanvasFrame,
+ canvasCbSaveState);
+
+ nsFrameConstructorSaveState docElementCbSaveState;
+ if (mCanvasFrame != mDocElementContainingBlock) {
+ mDocElementContainingBlock->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ state.PushAbsoluteContainingBlock(mDocElementContainingBlock,
+ mDocElementContainingBlock,
+ docElementCbSaveState);
+ }
+
+ // The rules from CSS 2.1, section 9.2.4, have already been applied
+ // by the style system, so we can assume that display->mDisplay is
+ // either NONE, BLOCK, or TABLE.
+
+ // contentFrame is the primary frame for the root element. frameList contains
+ // the children of the initial containing block.
+ //
+ // The first of those frames is usually `contentFrame`, but it can be
+ // different, in particular if the root frame is positioned, in which case
+ // contentFrame is the out-of-flow frame and frameList.FirstChild() is the
+ // placeholder.
+ //
+ // The rest of the frames in frameList are the anonymous content of the canvas
+ // frame.
+ nsContainerFrame* contentFrame;
+ nsFrameList frameList;
+ bool processChildren = false;
+
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ if (aDocElement->IsSVGElement()) {
+ if (!aDocElement->IsSVGElement(nsGkAtoms::svg)) {
+ return nullptr;
+ }
+ // We're going to call the right function ourselves, so no need to give a
+ // function to this FrameConstructionData.
+
+ // XXXbz on the other hand, if we converted this whole function to
+ // FrameConstructionData/Item, then we'd need the right function
+ // here... but would probably be able to get away with less code in this
+ // function in general.
+ static constexpr FrameConstructionData rootSVGData;
+ AutoFrameConstructionItem item(this, &rootSVGData, aDocElement,
+ do_AddRef(computedStyle), true);
+
+ contentFrame = static_cast<nsContainerFrame*>(ConstructOuterSVG(
+ state, item, mDocElementContainingBlock, display, frameList));
+ } else if (display->mDisplay == StyleDisplay::Flex ||
+ display->mDisplay == StyleDisplay::WebkitBox ||
+ display->mDisplay == StyleDisplay::Grid) {
+ auto func = [&] {
+ if (display->mDisplay == StyleDisplay::Grid) {
+ return NS_NewGridContainerFrame;
+ }
+ return NS_NewFlexContainerFrame;
+ }();
+ contentFrame = func(mPresShell, computedStyle);
+ InitAndRestoreFrame(
+ state, aDocElement,
+ state.GetGeometricParent(*display, mDocElementContainingBlock),
+ contentFrame);
+ state.AddChild(contentFrame, frameList, aDocElement,
+ mDocElementContainingBlock);
+ processChildren = true;
+
+ contentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (contentFrame->IsAbsPosContainingBlock()) {
+ state.PushAbsoluteContainingBlock(contentFrame, contentFrame,
+ absoluteSaveState);
+ }
+ } else if (display->mDisplay == StyleDisplay::Table) {
+ // We're going to call the right function ourselves, so no need to give a
+ // function to this FrameConstructionData.
+
+ // XXXbz on the other hand, if we converted this whole function to
+ // FrameConstructionData/Item, then we'd need the right function
+ // here... but would probably be able to get away with less code in this
+ // function in general.
+ static constexpr FrameConstructionData rootTableData;
+ AutoFrameConstructionItem item(this, &rootTableData, aDocElement,
+ do_AddRef(computedStyle), true);
+
+ // if the document is a table then just populate it.
+ contentFrame = static_cast<nsContainerFrame*>(ConstructTable(
+ state, item, mDocElementContainingBlock, display, frameList));
+ } else if (display->DisplayInside() == StyleDisplayInside::Ruby) {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructBlockRubyFrame);
+ AutoFrameConstructionItem item(this, &data, aDocElement,
+ do_AddRef(computedStyle), true);
+ contentFrame = static_cast<nsContainerFrame*>(ConstructBlockRubyFrame(
+ state, item,
+ state.GetGeometricParent(*display, mDocElementContainingBlock), display,
+ frameList));
+ } else {
+ MOZ_ASSERT(display->mDisplay == StyleDisplay::Block ||
+ display->mDisplay == StyleDisplay::FlowRoot,
+ "Unhandled display type for root element");
+ contentFrame = NS_NewBlockFormattingContext(mPresShell, computedStyle);
+ ConstructBlock(
+ state, aDocElement,
+ state.GetGeometricParent(*display, mDocElementContainingBlock),
+ mDocElementContainingBlock, computedStyle, &contentFrame, frameList,
+ contentFrame->IsAbsPosContainingBlock() ? contentFrame : nullptr);
+ }
+
+ MOZ_ASSERT(frameList.FirstChild());
+ MOZ_ASSERT(frameList.FirstChild()->GetContent() == aDocElement);
+ MOZ_ASSERT(contentFrame);
+
+ MOZ_ASSERT(
+ processChildren ? !mRootElementFrame : mRootElementFrame == contentFrame,
+ "unexpected mRootElementFrame");
+ if (processChildren) {
+ mRootElementFrame = contentFrame;
+ }
+
+ // Figure out which frame has the main style for the document element,
+ // assigning it to mRootElementStyleFrame.
+ // Backgrounds should be propagated from that frame to the viewport.
+ contentFrame->GetParentComputedStyle(&mRootElementStyleFrame);
+ bool isChild = mRootElementStyleFrame &&
+ mRootElementStyleFrame->GetParent() == contentFrame;
+ if (!isChild) {
+ mRootElementStyleFrame = mRootElementFrame;
+ }
+
+ if (processChildren) {
+ // Still need to process the child content
+ nsFrameList childList;
+
+ NS_ASSERTION(
+ !contentFrame->IsBlockFrameOrSubclass() && !contentFrame->IsSVGFrame(),
+ "Only XUL frames should reach here");
+
+ nsFrameConstructorSaveState floatSaveState;
+ state.MaybePushFloatContainingBlock(contentFrame, floatSaveState);
+
+ ProcessChildren(state, aDocElement, computedStyle, contentFrame, true,
+ childList, false);
+
+ // Set the initial child lists
+ contentFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ }
+
+ nsIFrame* newFrame = frameList.FirstChild();
+ // set the primary frame
+ aDocElement->SetPrimaryFrame(contentFrame);
+ mDocElementContainingBlock->AppendFrames(FrameChildListID::Principal,
+ std::move(frameList));
+
+ // NOTE(emilio): This is in the reverse order compared to normal anonymous
+ // children. We usually generate anonymous kids first, then non-anonymous,
+ // but we generate the doc element frame the other way around. This is fine
+ // either way, but generating anonymous children in a different order requires
+ // changing nsCanvasFrame (and a whole lot of other potentially unknown code)
+ // to look at the last child to find the root frame rather than the first
+ // child.
+ ConstructAnonymousContentForCanvas(
+ state, mCanvasFrame, mRootElementFrame->GetContent(), frameList);
+ mCanvasFrame->AppendFrames(FrameChildListID::Principal, std::move(frameList));
+
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructRootFrame() {
+ AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ConstructRootFrame",
+ LAYOUT_FrameConstruction);
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+
+ // --------- BUILD VIEWPORT -----------
+ RefPtr<ComputedStyle> viewportPseudoStyle =
+ styleSet->ResolveInheritingAnonymousBoxStyle(PseudoStyleType::viewport,
+ nullptr);
+ ViewportFrame* viewportFrame =
+ NS_NewViewportFrame(mPresShell, viewportPseudoStyle);
+
+ // XXXbz do we _have_ to pass a null content pointer to that frame?
+ // Would it really kill us to pass in the root element or something?
+ // What would that break?
+ viewportFrame->Init(nullptr, nullptr, nullptr);
+
+ viewportFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Bind the viewport frame to the root view
+ if (nsView* rootView = mPresShell->GetViewManager()->GetRootView()) {
+ viewportFrame->SetView(rootView);
+ viewportFrame->SyncFrameViewProperties(rootView);
+ rootView->SetNeedsWindowPropertiesSync();
+ }
+
+ // Make it an absolute container for fixed-pos elements
+ viewportFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ viewportFrame->MarkAsAbsoluteContainingBlock();
+
+ return viewportFrame;
+}
+
+void nsCSSFrameConstructor::SetUpDocElementContainingBlock(
+ nsIContent* aDocElement) {
+ MOZ_ASSERT(aDocElement, "No element?");
+ MOZ_ASSERT(!aDocElement->GetParent(), "Not root content?");
+ MOZ_ASSERT(aDocElement->GetUncomposedDoc(), "Not in a document?");
+ MOZ_ASSERT(aDocElement->GetUncomposedDoc()->GetRootElement() == aDocElement,
+ "Not the root of the document?");
+
+ /*
+ how the root frame hierarchy should look
+
+ Galley presentation, with scrolling:
+
+ ViewportFrame [fixed-cb]
+ nsHTMLScrollFrame (if needed)
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, SVGOuterSVGFrame,
+ nsTableWrapperFrame, nsPlaceholderFrame)
+
+ Print presentation, non-XUL
+
+ ViewportFrame
+ nsCanvasFrame
+ nsPageSequenceFrame
+ PrintedSheetFrame
+ nsPageFrame
+ nsPageContentFrame [fixed-cb]
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, SVGOuterSVGFrame,
+ nsTableWrapperFrame, nsPlaceholderFrame)
+
+ Print-preview presentation, non-XUL
+
+ ViewportFrame
+ nsHTMLScrollFrame
+ nsCanvasFrame
+ nsPageSequenceFrame
+ PrintedSheetFrame
+ nsPageFrame
+ nsPageContentFrame [fixed-cb]
+ nsCanvasFrame [abs-cb]
+ root element frame (nsBlockFrame, SVGOuterSVGFrame,
+ nsTableWrapperFrame,
+ nsPlaceholderFrame)
+
+ Print/print preview of XUL is not supported.
+ [fixed-cb]: the default containing block for fixed-pos content
+ [abs-cb]: the default containing block for abs-pos content
+
+ Meaning of nsCSSFrameConstructor fields:
+ mRootElementFrame is "root element frame". This is the primary frame for
+ the root element.
+ mDocElementContainingBlock is the parent of mRootElementFrame
+ (i.e. nsCanvasFrame)
+ mPageSequenceFrame is the nsPageSequenceFrame, or null if there isn't
+ one
+ */
+
+ // --------- CREATE ROOT FRAME -------
+
+ // Create the root frame. The document element's frame is a child of the
+ // root frame.
+ //
+ // The root frame serves two purposes:
+ // - reserves space for any margins needed for the document element's frame
+ // - renders the document element's background. This ensures the background
+ // covers the entire canvas as specified by the CSS2 spec
+
+ nsPresContext* presContext = mPresShell->GetPresContext();
+ const bool isPaginated = presContext->IsRootPaginatedDocument();
+
+ const bool isHTML = aDocElement->IsHTMLElement();
+ const bool isXUL = !isHTML && aDocElement->IsXULElement();
+
+ const bool isScrollable = [&] {
+ if (isPaginated) {
+ return presContext->HasPaginatedScrolling();
+ }
+ // Never create scrollbars for XUL documents or top level XHTML documents
+ // that disable scrolling.
+ if (isXUL) {
+ return false;
+ }
+ if (aDocElement->OwnerDoc()->ChromeRulesEnabled() &&
+ aDocElement->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::scrolling, nsGkAtoms::_false,
+ eCaseMatters)) {
+ return false;
+ }
+ return true;
+ }();
+
+ nsContainerFrame* viewportFrame =
+ static_cast<nsContainerFrame*>(GetRootFrame());
+ ComputedStyle* viewportPseudoStyle = viewportFrame->Style();
+
+ nsCanvasFrame* rootCanvasFrame =
+ NS_NewCanvasFrame(mPresShell, viewportPseudoStyle);
+ PseudoStyleType rootPseudo = PseudoStyleType::canvas;
+ mCanvasFrame = rootCanvasFrame;
+ mDocElementContainingBlock = rootCanvasFrame;
+
+ // --------- IF SCROLLABLE WRAP IN SCROLLFRAME --------
+
+ // If the device supports scrolling (e.g., in galley mode on the screen and
+ // for print-preview, but not when printing), then create a scroll frame that
+ // will act as the scrolling mechanism for the viewport.
+ // XXX Do we even need a viewport when printing to a printer?
+
+ // We no longer need to do overflow propagation here. It's taken care of
+ // when we construct frames for the element whose overflow might be
+ // propagated
+ NS_ASSERTION(!isScrollable || !isXUL,
+ "XUL documents should never be scrollable - see above");
+
+ nsContainerFrame* newFrame = rootCanvasFrame;
+ RefPtr<ComputedStyle> rootPseudoStyle;
+ // we must create a state because if the scrollbars are GFX it needs the
+ // state to build the scrollbar frames.
+ nsFrameConstructorState state(mPresShell, nullptr, nullptr, nullptr);
+
+ // Start off with the viewport as parent; we'll adjust it as needed.
+ nsContainerFrame* parentFrame = viewportFrame;
+
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ // If paginated, make sure we don't put scrollbars in
+ if (!isScrollable) {
+ rootPseudoStyle = styleSet->ResolveInheritingAnonymousBoxStyle(
+ rootPseudo, viewportPseudoStyle);
+ } else {
+ rootPseudo = PseudoStyleType::scrolledCanvas;
+
+ // Build the frame. We give it the content we are wrapping which is the
+ // document element, the root frame, the parent view port frame, and we
+ // should get back the new frame and the scrollable view if one was
+ // created.
+
+ // resolve a context for the scrollframe
+ RefPtr<ComputedStyle> computedStyle =
+ styleSet->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::viewportScroll, viewportPseudoStyle);
+
+ // Note that the viewport scrollframe is always built with
+ // overflow:auto style. This forces the scroll frame to create
+ // anonymous content for both scrollbars. This is necessary even
+ // if the HTML or BODY elements are overriding the viewport
+ // scroll style to 'hidden' --- dynamic style changes might put
+ // scrollbars back on the viewport and we don't want to have to
+ // reframe the viewport to create the scrollbar content.
+ newFrame = nullptr;
+ rootPseudoStyle =
+ BeginBuildingScrollFrame(state, aDocElement, computedStyle,
+ viewportFrame, rootPseudo, true, newFrame);
+ parentFrame = newFrame;
+ }
+
+ rootCanvasFrame->SetComputedStyleWithoutNotification(rootPseudoStyle);
+ rootCanvasFrame->Init(aDocElement, parentFrame, nullptr);
+
+ if (isScrollable) {
+ FinishBuildingScrollFrame(parentFrame, rootCanvasFrame);
+ }
+
+ if (isPaginated) {
+ // Create a page sequence frame
+ {
+ RefPtr<ComputedStyle> pageSequenceStyle =
+ styleSet->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::pageSequence, viewportPseudoStyle);
+ mPageSequenceFrame =
+ NS_NewPageSequenceFrame(mPresShell, pageSequenceStyle);
+ mPageSequenceFrame->Init(aDocElement, rootCanvasFrame, nullptr);
+ SetInitialSingleChild(rootCanvasFrame, mPageSequenceFrame);
+ }
+
+ // Create the first printed sheet frame, as the sole child (for now) of our
+ // page sequence frame (mPageSequenceFrame).
+ auto* printedSheetFrame =
+ ConstructPrintedSheetFrame(mPresShell, mPageSequenceFrame, nullptr);
+ SetInitialSingleChild(mPageSequenceFrame, printedSheetFrame);
+
+ MOZ_ASSERT(!mNextPageContentFramePageName,
+ "Next page name should not have been set.");
+
+ // Create the first page, as the sole child (for now) of the printed sheet
+ // frame that we just created.
+ nsCanvasFrame* canvasFrame;
+ nsContainerFrame* pageFrame =
+ ConstructPageFrame(mPresShell, printedSheetFrame, nullptr, canvasFrame);
+ pageFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+ SetInitialSingleChild(printedSheetFrame, pageFrame);
+
+ // The eventual parent of the document element frame.
+ // XXX should this be set for every new page (in ConstructPageFrame)?
+ mDocElementContainingBlock = canvasFrame;
+ }
+
+ if (viewportFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ SetInitialSingleChild(viewportFrame, newFrame);
+ } else {
+ viewportFrame->AppendFrames(FrameChildListID::Principal,
+ nsFrameList(newFrame, newFrame));
+ }
+}
+
+void nsCSSFrameConstructor::ConstructAnonymousContentForCanvas(
+ nsFrameConstructorState& aState, nsContainerFrame* aFrame,
+ nsIContent* aDocElement, nsFrameList& aFrameList) {
+ NS_ASSERTION(aFrame->IsCanvasFrame(), "aFrame should be canvas frame!");
+ MOZ_ASSERT(mRootElementFrame->GetContent() == aDocElement);
+
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
+ GetAnonymousContent(aDocElement, aFrame, anonymousItems);
+ if (anonymousItems.IsEmpty()) {
+ return;
+ }
+
+ AutoFrameConstructionItemList itemsToConstruct(this);
+ AutoFrameConstructionPageName pageNameTracker(aState, aFrame);
+ AddFCItemsForAnonymousContent(aState, aFrame, anonymousItems,
+ itemsToConstruct, pageNameTracker);
+ ConstructFramesFromItemList(aState, itemsToConstruct, aFrame,
+ /* aParentIsWrapperAnonBox = */ false,
+ aFrameList);
+}
+
+PrintedSheetFrame* nsCSSFrameConstructor::ConstructPrintedSheetFrame(
+ PresShell* aPresShell, nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevSheetFrame) {
+ RefPtr<ComputedStyle> printedSheetPseudoStyle =
+ aPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::printedSheet);
+
+ auto* printedSheetFrame =
+ NS_NewPrintedSheetFrame(aPresShell, printedSheetPseudoStyle);
+
+ printedSheetFrame->Init(nullptr, aParentFrame, aPrevSheetFrame);
+
+ return printedSheetFrame;
+}
+
+nsContainerFrame* nsCSSFrameConstructor::ConstructPageFrame(
+ PresShell* aPresShell, nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevPageFrame, nsCanvasFrame*& aCanvasFrame) {
+ ServoStyleSet* styleSet = aPresShell->StyleSet();
+
+ RefPtr<ComputedStyle> pagePseudoStyle =
+ styleSet->ResolveNonInheritingAnonymousBoxStyle(PseudoStyleType::page);
+
+ nsContainerFrame* pageFrame = NS_NewPageFrame(aPresShell, pagePseudoStyle);
+
+ // Initialize the page frame and force it to have a view. This makes printing
+ // of the pages easier and faster.
+ pageFrame->Init(nullptr, aParentFrame, aPrevPageFrame);
+
+ RefPtr<const nsAtom> pageName;
+ if (mNextPageContentFramePageName) {
+ pageName = mNextPageContentFramePageName.forget();
+ } else if (aPrevPageFrame) {
+ pageName = aPrevPageFrame->ComputePageValue();
+ MOZ_ASSERT(pageName,
+ "Page name from prev-in-flow should not have been null");
+ }
+ RefPtr<ComputedStyle> pageContentPseudoStyle =
+ styleSet->ResolvePageContentStyle(pageName,
+ StylePagePseudoClassFlags::NONE);
+
+ nsContainerFrame* pageContentFrame = NS_NewPageContentFrame(
+ aPresShell, pageContentPseudoStyle, pageName.forget());
+
+ nsPageContentFrame* prevPageContentFrame = nullptr;
+ if (aPrevPageFrame) {
+ MOZ_ASSERT(aPrevPageFrame->IsPageFrame());
+ prevPageContentFrame =
+ static_cast<nsPageFrame*>(aPrevPageFrame)->PageContentFrame();
+ }
+ pageContentFrame->Init(nullptr, pageFrame, prevPageContentFrame);
+ if (!prevPageContentFrame) {
+ // The canvas is an inheriting anon box, so needs to be "owned" by the page
+ // content.
+ pageContentFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+ }
+ SetInitialSingleChild(pageFrame, pageContentFrame);
+ // Make it an absolute container for fixed-pos elements
+ pageContentFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ pageContentFrame->MarkAsAbsoluteContainingBlock();
+
+ RefPtr<ComputedStyle> canvasPseudoStyle =
+ styleSet->ResolveInheritingAnonymousBoxStyle(PseudoStyleType::canvas,
+ pageContentPseudoStyle);
+
+ aCanvasFrame = NS_NewCanvasFrame(aPresShell, canvasPseudoStyle);
+
+ nsIFrame* prevCanvasFrame = nullptr;
+ if (prevPageContentFrame) {
+ prevCanvasFrame = prevPageContentFrame->PrincipalChildList().FirstChild();
+ NS_ASSERTION(prevCanvasFrame, "missing canvas frame");
+ }
+ aCanvasFrame->Init(nullptr, pageContentFrame, prevCanvasFrame);
+ SetInitialSingleChild(pageContentFrame, aCanvasFrame);
+ return pageFrame;
+}
+
+/* static */
+nsIFrame* nsCSSFrameConstructor::CreatePlaceholderFrameFor(
+ PresShell* aPresShell, nsIContent* aContent, nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame, nsIFrame* aPrevInFlow,
+ nsFrameState aTypeBit) {
+ RefPtr<ComputedStyle> placeholderStyle =
+ aPresShell->StyleSet()->ResolveStyleForPlaceholder();
+
+ // The placeholder frame gets a pseudo style.
+ nsPlaceholderFrame* placeholderFrame =
+ NS_NewPlaceholderFrame(aPresShell, placeholderStyle, aTypeBit);
+
+ placeholderFrame->Init(aContent, aParentFrame, aPrevInFlow);
+
+ // Associate the placeholder/out-of-flow with each other.
+ placeholderFrame->SetOutOfFlowFrame(aFrame);
+ aFrame->SetProperty(nsIFrame::PlaceholderFrameProperty(), placeholderFrame);
+
+ aFrame->AddStateBits(NS_FRAME_OUT_OF_FLOW);
+
+ return placeholderFrame;
+}
+
+// Clears any lazy bits set in the range [aStartContent, aEndContent). If
+// aEndContent is null, that means to clear bits in all siblings starting with
+// aStartContent. aStartContent must not be null unless aEndContent is also
+// null. We do this so that when new children are inserted under elements whose
+// frame is a leaf the new children don't cause us to try to construct frames
+// for the existing children again.
+static inline void ClearLazyBits(nsIContent* aStartContent,
+ nsIContent* aEndContent) {
+ MOZ_ASSERT(aStartContent || !aEndContent,
+ "Must have start child if we have an end child");
+
+ for (nsIContent* cur = aStartContent; cur != aEndContent;
+ cur = cur->GetNextSibling()) {
+ cur->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ }
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructSelectFrame(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList) {
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ // Construct a frame-based listbox or combobox
+ dom::HTMLSelectElement* sel = dom::HTMLSelectElement::FromNode(content);
+ MOZ_ASSERT(sel);
+ if (sel->IsCombobox()) {
+ // Construct a frame-based combo box.
+ // The frame-based combo box is built out of three parts. A display area, a
+ // button and a dropdown list. The display area and button are created
+ // through anonymous content. The drop-down list's frame is created
+ // explicitly. The combobox frame shares its content with the drop-down
+ // list.
+ nsComboboxControlFrame* comboboxFrame =
+ NS_NewComboboxControlFrame(mPresShell, computedStyle);
+
+ // Save the history state so we don't restore during construction
+ // since the complete tree is required before we restore.
+ nsILayoutHistoryState* historyState = aState.mFrameState;
+ aState.mFrameState = nullptr;
+ // Initialize the combobox frame
+ InitAndRestoreFrame(aState, content,
+ aState.GetGeometricParent(*aStyleDisplay, aParentFrame),
+ comboboxFrame);
+
+ comboboxFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ aState.AddChild(comboboxFrame, aFrameList, content, aParentFrame);
+
+ // Resolve pseudo element style for the dropdown list
+ RefPtr<ComputedStyle> listStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::dropDownList, computedStyle);
+
+ // child frames of combobox frame
+ nsFrameList childList;
+
+ // Create display and button frames from the combobox's anonymous content.
+ // The anonymous content is appended to existing anonymous content for this
+ // element (the scrollbars).
+ //
+ // nsComboboxControlFrame needs special frame creation behavior for its
+ // first piece of anonymous content, which means that we can't take the
+ // normal ProcessChildren path.
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 2> newAnonymousItems;
+ DebugOnly<nsresult> rv =
+ GetAnonymousContent(content, comboboxFrame, newAnonymousItems);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ MOZ_ASSERT(!newAnonymousItems.IsEmpty());
+
+ // Manually create a frame for the special NAC.
+ MOZ_ASSERT(newAnonymousItems[0].mContent ==
+ comboboxFrame->GetDisplayNode());
+ newAnonymousItems.RemoveElementAt(0);
+ nsIFrame* customFrame = comboboxFrame->CreateFrameForDisplayNode();
+ MOZ_ASSERT(customFrame);
+ childList.AppendFrame(nullptr, customFrame);
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(comboboxFrame, floatSaveState);
+
+ // The other piece of NAC can take the normal path.
+ AutoFrameConstructionItemList fcItems(this);
+ AutoFrameConstructionPageName pageNameTracker(aState, comboboxFrame);
+ AddFCItemsForAnonymousContent(aState, comboboxFrame, newAnonymousItems,
+ fcItems, pageNameTracker);
+ ConstructFramesFromItemList(aState, fcItems, comboboxFrame,
+ /* aParentIsWrapperAnonBox = */ false,
+ childList);
+
+ comboboxFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+
+ aState.mFrameState = historyState;
+ if (aState.mFrameState) {
+ // Restore frame state for the entire subtree of |comboboxFrame|.
+ RestoreFrameState(comboboxFrame, aState.mFrameState);
+ }
+ return comboboxFrame;
+ }
+
+ // Listbox, not combobox
+ nsContainerFrame* listFrame =
+ NS_NewListControlFrame(mPresShell, computedStyle);
+
+ nsContainerFrame* scrolledFrame =
+ NS_NewSelectsAreaFrame(mPresShell, computedStyle);
+
+ // ******* this code stolen from Initialze ScrollFrame ********
+ // please adjust this code to use BuildScrollFrame.
+
+ InitializeListboxSelect(aState, listFrame, scrolledFrame, content,
+ aParentFrame, computedStyle, aFrameList);
+
+ return listFrame;
+}
+
+void nsCSSFrameConstructor::InitializeListboxSelect(
+ nsFrameConstructorState& aState, nsContainerFrame* scrollFrame,
+ nsContainerFrame* scrolledFrame, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, ComputedStyle* aComputedStyle,
+ nsFrameList& aFrameList) {
+ // Initialize it
+ nsContainerFrame* geometricParent =
+ aState.GetGeometricParent(*aComputedStyle->StyleDisplay(), aParentFrame);
+
+ // We don't call InitAndRestoreFrame for scrollFrame because we can only
+ // restore the frame state after its parts have been created (in particular,
+ // the scrollable view). So we have to split Init and Restore.
+
+ scrollFrame->Init(aContent, geometricParent, nullptr);
+ aState.AddChild(scrollFrame, aFrameList, aContent, aParentFrame);
+ BuildScrollFrame(aState, aContent, aComputedStyle, scrolledFrame,
+ geometricParent, scrollFrame);
+ if (aState.mFrameState) {
+ // Restore frame state for the scroll frame
+ RestoreFrameStateFor(scrollFrame, aState.mFrameState);
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(scrolledFrame, floatSaveState);
+
+ // Process children
+ nsFrameList childList;
+
+ ProcessChildren(aState, aContent, aComputedStyle, scrolledFrame, false,
+ childList, false);
+
+ // Set the scrolled frame's initial child lists
+ scrolledFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructFieldSetFrame(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList) {
+ AutoRestore<bool> savedHasRenderedLegend(aState.mHasRenderedLegend);
+ aState.mHasRenderedLegend = false;
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsContainerFrame* fieldsetFrame =
+ NS_NewFieldSetFrame(mPresShell, computedStyle);
+
+ // Initialize it
+ InitAndRestoreFrame(aState, content,
+ aState.GetGeometricParent(*aStyleDisplay, aParentFrame),
+ fieldsetFrame);
+
+ fieldsetFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Resolve style and initialize the frame
+ RefPtr<ComputedStyle> fieldsetContentStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::fieldsetContent, computedStyle);
+
+ const nsStyleDisplay* fieldsetContentDisplay =
+ fieldsetContentStyle->StyleDisplay();
+ bool isScrollable = fieldsetContentDisplay->IsScrollableOverflow();
+ nsContainerFrame* scrollFrame = nullptr;
+ if (isScrollable) {
+ fieldsetContentStyle = BeginBuildingScrollFrame(
+ aState, content, fieldsetContentStyle, fieldsetFrame,
+ PseudoStyleType::scrolledContent, false, scrollFrame);
+ }
+
+ // Create the inner ::-moz-fieldset-content frame.
+ nsContainerFrame* contentFrameTop;
+ nsContainerFrame* contentFrame;
+ auto* parent = scrollFrame ? scrollFrame : fieldsetFrame;
+ MOZ_ASSERT(fieldsetContentDisplay->DisplayOutside() ==
+ StyleDisplayOutside::Block);
+ switch (fieldsetContentDisplay->DisplayInside()) {
+ case StyleDisplayInside::Flex:
+ contentFrame = NS_NewFlexContainerFrame(mPresShell, fieldsetContentStyle);
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ break;
+ case StyleDisplayInside::Grid:
+ contentFrame = NS_NewGridContainerFrame(mPresShell, fieldsetContentStyle);
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ break;
+ default: {
+ MOZ_ASSERT(fieldsetContentDisplay->mDisplay == StyleDisplay::Block,
+ "bug in StyleAdjuster::adjust_for_fieldset_content?");
+
+ contentFrame =
+ NS_NewBlockFormattingContext(mPresShell, fieldsetContentStyle);
+ if (fieldsetContentStyle->StyleColumn()->IsColumnContainerStyle()) {
+ contentFrameTop = BeginBuildingColumns(
+ aState, content, parent, contentFrame, fieldsetContentStyle);
+ } else {
+ // No need to create column container. Initialize content frame.
+ InitAndRestoreFrame(aState, content, parent, contentFrame);
+ contentFrameTop = contentFrame;
+ }
+
+ break;
+ }
+ }
+
+ aState.AddChild(fieldsetFrame, aFrameList, content, aParentFrame);
+
+ // Process children
+ nsFrameConstructorSaveState absoluteSaveState;
+ nsFrameList childList;
+
+ contentFrameTop->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (fieldsetFrame->IsAbsPosContainingBlock()) {
+ aState.PushAbsoluteContainingBlock(contentFrameTop, fieldsetFrame,
+ absoluteSaveState);
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(contentFrame, floatSaveState);
+
+ ProcessChildren(aState, content, computedStyle, contentFrame, true, childList,
+ true);
+ nsFrameList fieldsetKids;
+ fieldsetKids.AppendFrame(nullptr,
+ scrollFrame ? scrollFrame : contentFrameTop);
+
+ if (!MayNeedToCreateColumnSpanSiblings(contentFrame, childList)) {
+ // Set the inner frame's initial child lists.
+ contentFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ } else {
+ // Extract any initial non-column-span kids, and put them in inner frame's
+ // child list.
+ nsFrameList initialNonColumnSpanKids =
+ childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+ contentFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(initialNonColumnSpanKids));
+
+ if (childList.NotEmpty()) {
+ nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
+ aState, contentFrame, childList,
+ // Column content should never be a absolute/fixed positioned
+ // containing block. Pass nullptr as aPositionedFrame.
+ nullptr);
+ FinishBuildingColumns(aState, contentFrameTop, contentFrame,
+ columnSpanSiblings);
+ }
+ }
+
+ if (isScrollable) {
+ FinishBuildingScrollFrame(scrollFrame, contentFrameTop);
+ }
+
+ // We use AppendFrames here because the rendered legend will already
+ // be present in the principal child list if it exists.
+ fieldsetFrame->AppendFrames(FrameChildListID::NoReflowPrincipal,
+ std::move(fieldsetKids));
+
+ return fieldsetFrame;
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDetailsData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ if (!StaticPrefs::layout_details_force_block_layout()) {
+ return nullptr;
+ }
+ static constexpr FrameConstructionData sBlockData[2] = {
+ {&nsCSSFrameConstructor::ConstructNonScrollableBlock},
+ {&nsCSSFrameConstructor::ConstructScrollableBlock},
+ };
+ return &sBlockData[aStyle.StyleDisplay()->IsScrollableOverflow()];
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructBlockRubyFrame(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList) {
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, computedStyle);
+ nsContainerFrame* newFrame = blockFrame;
+ nsContainerFrame* geometricParent =
+ aState.GetGeometricParent(*aStyleDisplay, aParentFrame);
+ AutoFrameConstructionPageName pageNameTracker(aState, blockFrame);
+ if ((aItem.mFCData->mBits & FCDATA_MAY_NEED_SCROLLFRAME) &&
+ aStyleDisplay->IsScrollableOverflow()) {
+ nsContainerFrame* scrollframe = nullptr;
+ BuildScrollFrame(aState, content, computedStyle, blockFrame,
+ geometricParent, scrollframe);
+ newFrame = scrollframe;
+ } else {
+ InitAndRestoreFrame(aState, content, geometricParent, blockFrame);
+ }
+
+ RefPtr<ComputedStyle> rubyStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::blockRubyContent, computedStyle);
+ nsContainerFrame* rubyFrame = NS_NewRubyFrame(mPresShell, rubyStyle);
+ InitAndRestoreFrame(aState, content, blockFrame, rubyFrame);
+ SetInitialSingleChild(blockFrame, rubyFrame);
+ blockFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ aState.AddChild(newFrame, aFrameList, content, aParentFrame);
+
+ if (!mRootElementFrame) {
+ mRootElementFrame = newFrame;
+ }
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ blockFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (newFrame->IsAbsPosContainingBlock()) {
+ aState.PushAbsoluteContainingBlock(blockFrame, blockFrame,
+ absoluteSaveState);
+ }
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(blockFrame, floatSaveState);
+
+ nsFrameList childList;
+ ProcessChildren(aState, content, rubyStyle, rubyFrame, true, childList, false,
+ nullptr);
+ rubyFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+
+ return newFrame;
+}
+
+static nsIFrame* FindAncestorWithGeneratedContentPseudo(nsIFrame* aFrame) {
+ for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
+ NS_ASSERTION(f->IsGeneratedContentFrame(),
+ "should not have exited generated content");
+ auto pseudo = f->Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::before || pseudo == PseudoStyleType::after ||
+ pseudo == PseudoStyleType::marker)
+ return f;
+ }
+ return nullptr;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindTextData(const Text& aTextContent,
+ nsIFrame* aParentFrame) {
+ if (aParentFrame && IsFrameForSVG(aParentFrame)) {
+ if (!aParentFrame->IsInSVGTextSubtree()) {
+ return nullptr;
+ }
+
+ // FIXME(bug 1588477) Don't render stuff in display: contents / Shadow DOM
+ // subtrees, because TextCorrespondenceRecorder in the SVG text code doesn't
+ // really know how to deal with it. This kinda sucks. :(
+ if (aParentFrame->GetContent() != aTextContent.GetParent()) {
+ return nullptr;
+ }
+
+ static constexpr FrameConstructionData sSVGTextData(
+ NS_NewTextFrame, FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_SVG_TEXT);
+ return &sSVGTextData;
+ }
+
+ static constexpr FrameConstructionData sTextData(NS_NewTextFrame,
+ FCDATA_IS_LINE_PARTICIPANT);
+ return &sTextData;
+}
+
+void nsCSSFrameConstructor::ConstructTextFrame(
+ const FrameConstructionData* aData, nsFrameConstructorState& aState,
+ nsIContent* aContent, nsContainerFrame* aParentFrame,
+ ComputedStyle* aComputedStyle, nsFrameList& aFrameList) {
+ MOZ_ASSERT(aData, "Must have frame construction data");
+
+ nsIFrame* newFrame =
+ (*aData->mFunc.mCreationFunc)(mPresShell, aComputedStyle);
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, newFrame);
+
+ // We never need to create a view for a text frame.
+
+ if (newFrame->IsGeneratedContentFrame()) {
+ UniquePtr<nsGenConInitializer> initializer(
+ static_cast<nsGenConInitializer*>(
+ aContent->TakeProperty(nsGkAtoms::genConInitializerProperty)));
+ if (initializer) {
+ if (initializer->mNode.release()->InitTextFrame(
+ initializer->mList,
+ FindAncestorWithGeneratedContentPseudo(newFrame), newFrame)) {
+ (this->*(initializer->mDirtyAll))();
+ }
+ }
+ }
+
+ // Add the newly constructed frame to the flow
+ aFrameList.AppendFrame(nullptr, newFrame);
+
+ if (!aState.mCreatingExtraFrames || (aContent->IsInNativeAnonymousSubtree() &&
+ !aContent->GetPrimaryFrame())) {
+ aContent->SetPrimaryFrame(newFrame);
+ }
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDataByInt(int32_t aInt, const Element& aElement,
+ ComputedStyle& aComputedStyle,
+ const FrameConstructionDataByInt* aDataPtr,
+ uint32_t aDataLength) {
+ for (const FrameConstructionDataByInt *curData = aDataPtr,
+ *endData = aDataPtr + aDataLength;
+ curData != endData; ++curData) {
+ if (curData->mInt == aInt) {
+ const FrameConstructionData* data = &curData->mData;
+ if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
+ return data->mFunc.mDataGetter(aElement, aComputedStyle);
+ }
+
+ return data;
+ }
+ }
+
+ return nullptr;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDataByTag(const Element& aElement,
+ ComputedStyle& aStyle,
+ const FrameConstructionDataByTag* aDataPtr,
+ uint32_t aDataLength) {
+ const nsAtom* tag = aElement.NodeInfo()->NameAtom();
+ for (const FrameConstructionDataByTag *curData = aDataPtr,
+ *endData = aDataPtr + aDataLength;
+ curData != endData; ++curData) {
+ if (curData->mTag == tag) {
+ const FrameConstructionData* data = &curData->mData;
+ if (data->mBits & FCDATA_FUNC_IS_DATA_GETTER) {
+ return data->mFunc.mDataGetter(aElement, aStyle);
+ }
+
+ return data;
+ }
+ }
+
+ return nullptr;
+}
+
+#define SUPPRESS_FCDATA() FrameConstructionData(nullptr, FCDATA_SUPPRESS_FRAME)
+#define SIMPLE_INT_CREATE(_int, _func) \
+ { int32_t(_int), FrameConstructionData(_func) }
+#define SIMPLE_INT_CHAIN(_int, _func) \
+ { int32_t(_int), FrameConstructionData(_func) }
+#define COMPLEX_INT_CREATE(_int, _func) \
+ { int32_t(_int), FrameConstructionData(_func) }
+
+#define SIMPLE_TAG_CREATE(_tag, _func) \
+ { nsGkAtoms::_tag, FrameConstructionData(_func) }
+#define SIMPLE_TAG_CHAIN(_tag, _func) \
+ { nsGkAtoms::_tag, FrameConstructionData(_func) }
+#define COMPLEX_TAG_CREATE(_tag, _func) \
+ { nsGkAtoms::_tag, FrameConstructionData(_func) }
+
+static nsFieldSetFrame* GetFieldSetFrameFor(nsIFrame* aFrame) {
+ auto pseudo = aFrame->Style()->GetPseudoType();
+ if (pseudo == PseudoStyleType::fieldsetContent ||
+ pseudo == PseudoStyleType::scrolledContent ||
+ pseudo == PseudoStyleType::columnSet ||
+ pseudo == PseudoStyleType::columnContent) {
+ return GetFieldSetFrameFor(aFrame->GetParent());
+ }
+ return do_QueryFrame(aFrame);
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindHTMLData(const Element& aElement,
+ nsIFrame* aParentFrame,
+ ComputedStyle& aStyle) {
+ MOZ_ASSERT(aElement.IsHTMLElement());
+ NS_ASSERTION(!aParentFrame ||
+ aParentFrame->Style()->GetPseudoType() !=
+ PseudoStyleType::fieldsetContent ||
+ aParentFrame->GetParent()->IsFieldSetFrame(),
+ "Unexpected parent for fieldset content anon box");
+
+ if (aElement.IsInNativeAnonymousSubtree() &&
+ aElement.NodeInfo()->NameAtom() == nsGkAtoms::label &&
+ static_cast<nsFileControlFrame*>(do_QueryFrame(aParentFrame))) {
+ static constexpr FrameConstructionData sFileLabelData(
+ NS_NewFileControlLabelFrame);
+ return &sFileLabelData;
+ }
+
+ static constexpr FrameConstructionDataByTag sHTMLData[] = {
+ SIMPLE_TAG_CHAIN(img, nsCSSFrameConstructor::FindImgData),
+ SIMPLE_TAG_CHAIN(mozgeneratedcontentimage,
+ nsCSSFrameConstructor::FindGeneratedImageData),
+ {nsGkAtoms::br,
+ {NS_NewBRFrame, FCDATA_IS_LINE_PARTICIPANT | FCDATA_IS_LINE_BREAK}},
+ SIMPLE_TAG_CREATE(wbr, NS_NewWBRFrame),
+ SIMPLE_TAG_CHAIN(input, nsCSSFrameConstructor::FindInputData),
+ SIMPLE_TAG_CREATE(textarea, NS_NewTextControlFrame),
+ COMPLEX_TAG_CREATE(select, &nsCSSFrameConstructor::ConstructSelectFrame),
+ SIMPLE_TAG_CHAIN(object, nsCSSFrameConstructor::FindObjectData),
+ SIMPLE_TAG_CHAIN(embed, nsCSSFrameConstructor::FindObjectData),
+ COMPLEX_TAG_CREATE(fieldset,
+ &nsCSSFrameConstructor::ConstructFieldSetFrame),
+ SIMPLE_TAG_CREATE(frameset, NS_NewHTMLFramesetFrame),
+ SIMPLE_TAG_CREATE(iframe, NS_NewSubDocumentFrame),
+ {nsGkAtoms::button,
+ {ToCreationFunc(NS_NewHTMLButtonControlFrame),
+ FCDATA_ALLOW_BLOCK_STYLES | FCDATA_ALLOW_GRID_FLEX_COLUMN,
+ PseudoStyleType::buttonContent}},
+ SIMPLE_TAG_CHAIN(canvas, nsCSSFrameConstructor::FindCanvasData),
+ SIMPLE_TAG_CREATE(video, NS_NewHTMLVideoFrame),
+ SIMPLE_TAG_CREATE(audio, NS_NewHTMLAudioFrame),
+ SIMPLE_TAG_CREATE(progress, NS_NewProgressFrame),
+ SIMPLE_TAG_CREATE(meter, NS_NewMeterFrame),
+ SIMPLE_TAG_CHAIN(details, nsCSSFrameConstructor::FindDetailsData),
+ };
+
+ return FindDataByTag(aElement, aStyle, sHTMLData, ArrayLength(sHTMLData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindGeneratedImageData(const Element& aElement,
+ ComputedStyle&) {
+ if (!aElement.IsInNativeAnonymousSubtree()) {
+ return nullptr;
+ }
+
+ auto& generatedContent = static_cast<const GeneratedImageContent&>(aElement);
+ if (generatedContent.IsForListStyleImageMarker()) {
+ static constexpr FrameConstructionData sImgData(
+ NS_NewImageFrameForListStyleImage);
+ return &sImgData;
+ }
+
+ static constexpr FrameConstructionData sImgData(
+ NS_NewImageFrameForGeneratedContentIndex);
+ return &sImgData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindImgData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ if (nsImageFrame::ImageFrameTypeFor(aElement, aStyle) !=
+ nsImageFrame::ImageFrameType::ForElementRequest) {
+ // content: url gets handled by the generic code-path.
+ return nullptr;
+ }
+
+ static constexpr FrameConstructionData sImgData(NS_NewImageFrame);
+ return &sImgData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindImgControlData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ if (nsImageFrame::ImageFrameTypeFor(aElement, aStyle) !=
+ nsImageFrame::ImageFrameType::ForElementRequest) {
+ return nullptr;
+ }
+
+ static constexpr FrameConstructionData sImgControlData(
+ NS_NewImageControlFrame);
+ return &sImgControlData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindSearchControlData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ if (StaticPrefs::layout_forms_input_type_search_enabled()) {
+ static constexpr FrameConstructionData sSearchControlData(
+ NS_NewSearchControlFrame);
+ return &sSearchControlData;
+ }
+
+ static constexpr FrameConstructionData sTextControlData(
+ NS_NewTextControlFrame);
+ return &sTextControlData;
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindInputData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ static constexpr FrameConstructionDataByInt sInputData[] = {
+ SIMPLE_INT_CREATE(FormControlType::InputCheckbox,
+ ToCreationFunc(NS_NewCheckboxRadioFrame)),
+ SIMPLE_INT_CREATE(FormControlType::InputRadio,
+ ToCreationFunc(NS_NewCheckboxRadioFrame)),
+ SIMPLE_INT_CREATE(FormControlType::InputFile, NS_NewFileControlFrame),
+ SIMPLE_INT_CHAIN(FormControlType::InputImage,
+ nsCSSFrameConstructor::FindImgControlData),
+ SIMPLE_INT_CREATE(FormControlType::InputEmail, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputText, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputTel, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputUrl, NS_NewTextControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputRange, NS_NewRangeFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputPassword, NS_NewTextControlFrame),
+ {int32_t(FormControlType::InputColor),
+ {NS_NewColorControlFrame, 0, PseudoStyleType::buttonContent}},
+
+ SIMPLE_INT_CHAIN(FormControlType::InputSearch,
+ nsCSSFrameConstructor::FindSearchControlData),
+ SIMPLE_INT_CREATE(FormControlType::InputNumber, NS_NewNumberControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputTime, NS_NewDateTimeControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputDate, NS_NewDateTimeControlFrame),
+ SIMPLE_INT_CREATE(FormControlType::InputDatetimeLocal,
+ NS_NewDateTimeControlFrame),
+ // TODO: this is temporary until a frame is written: bug 888320
+ SIMPLE_INT_CREATE(FormControlType::InputMonth, NS_NewTextControlFrame),
+ // TODO: this is temporary until a frame is written: bug 888320
+ SIMPLE_INT_CREATE(FormControlType::InputWeek, NS_NewTextControlFrame),
+ {int32_t(FormControlType::InputSubmit),
+ {ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
+ PseudoStyleType::buttonContent}},
+ {int32_t(FormControlType::InputReset),
+ {ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
+ PseudoStyleType::buttonContent}},
+ {int32_t(FormControlType::InputButton),
+ {ToCreationFunc(NS_NewGfxButtonControlFrame), 0,
+ PseudoStyleType::buttonContent}}
+ // Keeping hidden inputs out of here on purpose for so they get frames by
+ // display (in practice, none).
+ };
+
+ auto controlType = HTMLInputElement::FromNode(aElement)->ControlType();
+
+ // radio and checkbox inputs with appearance:none should be constructed
+ // by display type. (Note that we're not checking that appearance is
+ // not (respectively) StyleAppearance::Radio and StyleAppearance::Checkbox.)
+ if ((controlType == FormControlType::InputCheckbox ||
+ controlType == FormControlType::InputRadio) &&
+ !aStyle.StyleDisplay()->HasAppearance()) {
+ return nullptr;
+ }
+
+ return FindDataByInt(int32_t(controlType), aElement, aStyle, sInputData,
+ ArrayLength(sInputData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindObjectData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ uint32_t type;
+ nsCOMPtr<nsIObjectLoadingContent> objContent =
+ do_QueryInterface(const_cast<Element*>(&aElement));
+ NS_ASSERTION(objContent,
+ "embed and object must implement "
+ "nsIObjectLoadingContent!");
+ objContent->GetDisplayedType(&type);
+
+ static constexpr FrameConstructionDataByInt sObjectData[] = {
+ // TODO(emilio): Can we remove the NS_NewEmptyFrame case and just use a
+ // subdocument frame here?
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_LOADING,
+ NS_NewEmptyFrame),
+ SIMPLE_INT_CREATE(nsIObjectLoadingContent::TYPE_DOCUMENT,
+ NS_NewSubDocumentFrame),
+ // Nothing for TYPE_FALLBACK so we'll construct frames by display there
+ };
+
+ return FindDataByInt((int32_t)type, aElement, aStyle, sObjectData,
+ ArrayLength(sObjectData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindCanvasData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ // We want to check whether script is enabled on the document that
+ // could be painting to the canvas. That's the owner document of
+ // the canvas, except when the owner document is a static document,
+ // in which case it's the original document it was cloned from.
+ Document* doc = aElement.OwnerDoc();
+ if (doc->IsStaticDocument()) {
+ doc = doc->GetOriginalDocument();
+ }
+ if (!doc->IsScriptEnabled()) {
+ return nullptr;
+ }
+
+ static constexpr FrameConstructionData sCanvasData(
+ NS_NewHTMLCanvasFrame, 0, PseudoStyleType::htmlCanvasContent);
+ return &sCanvasData;
+}
+
+static MOZ_NEVER_INLINE void DestroyFramesInList(PresShell* aPs,
+ nsFrameList& aList) {
+ nsIFrame::DestroyContext context(aPs);
+ aList.DestroyFrames(context);
+}
+
+void nsCSSFrameConstructor::ConstructFrameFromItemInternal(
+ FrameConstructionItem& aItem, nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame, nsFrameList& aFrameList) {
+ const FrameConstructionData* data = aItem.mFCData;
+ NS_ASSERTION(data, "Must have frame construction data");
+
+ uint32_t bits = data->mBits;
+
+ NS_ASSERTION(!(bits & FCDATA_FUNC_IS_DATA_GETTER),
+ "Should have dealt with this inside the data finder");
+
+ // Some sets of bits are not compatible with each other
+#define CHECK_ONLY_ONE_BIT(_bit1, _bit2) \
+ NS_ASSERTION(!(bits & _bit1) || !(bits & _bit2), \
+ "Only one of these bits should be set")
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
+ FCDATA_FORCE_NULL_ABSPOS_CONTAINER);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_WRAP_KIDS_IN_BLOCKS);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_IS_POPUP);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_SKIP_ABSPOS_PUSH);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
+ FCDATA_DISALLOW_GENERATED_CONTENT);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR, FCDATA_ALLOW_BLOCK_STYLES);
+ CHECK_ONLY_ONE_BIT(FCDATA_FUNC_IS_FULL_CTOR,
+ FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
+ CHECK_ONLY_ONE_BIT(FCDATA_WRAP_KIDS_IN_BLOCKS,
+ FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS);
+#undef CHECK_ONLY_ONE_BIT
+ NS_ASSERTION(!(bits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) ||
+ ((bits & FCDATA_FUNC_IS_FULL_CTOR) &&
+ data->mFunc.mFullConstructor ==
+ &nsCSSFrameConstructor::ConstructNonScrollableBlock),
+ "Unexpected FCDATA_FORCED_NON_SCROLLABLE_BLOCK flag");
+ MOZ_ASSERT(
+ !(bits & FCDATA_IS_WRAPPER_ANON_BOX) || (bits & FCDATA_USE_CHILD_ITEMS),
+ "Wrapper anon boxes should always have FCDATA_USE_CHILD_ITEMS");
+ MOZ_ASSERT(!(bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) ||
+ (bits & FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS),
+ "Need the block wrapper bit to create grid/flex/column.");
+
+ // Don't create a subdocument frame for iframes if we're creating extra frames
+ if (aState.mCreatingExtraFrames &&
+ aItem.mContent->IsHTMLElement(nsGkAtoms::iframe)) {
+ return;
+ }
+
+ nsIContent* const content = aItem.mContent;
+ nsIFrame* newFrame;
+ nsIFrame* primaryFrame;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+ const nsStyleDisplay* display = computedStyle->StyleDisplay();
+ if (bits & FCDATA_FUNC_IS_FULL_CTOR) {
+ newFrame = (this->*(data->mFunc.mFullConstructor))(
+ aState, aItem, aParentFrame, display, aFrameList);
+ MOZ_ASSERT(newFrame, "Full constructor failed");
+ primaryFrame = newFrame;
+ } else {
+ newFrame = (*data->mFunc.mCreationFunc)(mPresShell, computedStyle);
+
+ const bool allowOutOfFlow = !(bits & FCDATA_DISALLOW_OUT_OF_FLOW);
+ const bool isPopup = aItem.mIsPopup;
+
+ nsContainerFrame* geometricParent =
+ (isPopup || allowOutOfFlow)
+ ? aState.GetGeometricParent(*display, aParentFrame)
+ : aParentFrame;
+
+ // In the non-scrollframe case, primaryFrame and newFrame are equal; in the
+ // scrollframe case, newFrame is the scrolled frame while primaryFrame is
+ // the scrollframe.
+ if ((bits & FCDATA_MAY_NEED_SCROLLFRAME) &&
+ display->IsScrollableOverflow()) {
+ nsContainerFrame* scrollframe = nullptr;
+ BuildScrollFrame(aState, content, computedStyle, newFrame,
+ geometricParent, scrollframe);
+ primaryFrame = scrollframe;
+ } else {
+ InitAndRestoreFrame(aState, content, geometricParent, newFrame);
+ primaryFrame = newFrame;
+ }
+
+ // If we need to create a block formatting context to wrap our
+ // kids, do it now.
+ nsIFrame* maybeAbsoluteContainingBlockStyleFrame = primaryFrame;
+ nsIFrame* maybeAbsoluteContainingBlock = newFrame;
+ nsIFrame* possiblyLeafFrame = newFrame;
+ nsContainerFrame* outerFrame = nullptr;
+ if (bits & FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS) {
+ RefPtr<ComputedStyle> outerStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ data->mAnonBoxPseudo, computedStyle);
+#ifdef DEBUG
+ nsContainerFrame* containerFrame = do_QueryFrame(newFrame);
+ MOZ_ASSERT(containerFrame);
+#endif
+ nsContainerFrame* container = static_cast<nsContainerFrame*>(newFrame);
+ nsContainerFrame* innerFrame;
+ if (bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) {
+ switch (display->DisplayInside()) {
+ case StyleDisplayInside::Flex:
+ outerFrame = NS_NewFlexContainerFrame(mPresShell, outerStyle);
+ InitAndRestoreFrame(aState, content, container, outerFrame);
+ innerFrame = outerFrame;
+ break;
+ case StyleDisplayInside::Grid:
+ outerFrame = NS_NewGridContainerFrame(mPresShell, outerStyle);
+ InitAndRestoreFrame(aState, content, container, outerFrame);
+ innerFrame = outerFrame;
+ break;
+ default: {
+ innerFrame = NS_NewBlockFormattingContext(mPresShell, outerStyle);
+ if (outerStyle->StyleColumn()->IsColumnContainerStyle()) {
+ outerFrame = BeginBuildingColumns(aState, content, container,
+ innerFrame, outerStyle);
+ } else {
+ // No need to create column container. Initialize innerFrame.
+ InitAndRestoreFrame(aState, content, container, innerFrame);
+ outerFrame = innerFrame;
+ }
+ break;
+ }
+ }
+ } else {
+ innerFrame = NS_NewBlockFormattingContext(mPresShell, outerStyle);
+ InitAndRestoreFrame(aState, content, container, innerFrame);
+ outerFrame = innerFrame;
+ }
+
+ SetInitialSingleChild(container, outerFrame);
+
+ container->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Now figure out whether newFrame or outerFrame should be the
+ // absolute container.
+ if (outerFrame->IsAbsPosContainingBlock()) {
+ maybeAbsoluteContainingBlock = outerFrame;
+ maybeAbsoluteContainingBlockStyleFrame = outerFrame;
+ innerFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ }
+
+ // Our kids should go into the innerFrame.
+ newFrame = innerFrame;
+ }
+
+ aState.AddChild(primaryFrame, aFrameList, content, aParentFrame,
+ allowOutOfFlow, allowOutOfFlow);
+
+ nsContainerFrame* newFrameAsContainer = do_QueryFrame(newFrame);
+ if (newFrameAsContainer) {
+ // Process the child content if requested
+ nsFrameList childList;
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ if (bits & FCDATA_FORCE_NULL_ABSPOS_CONTAINER) {
+ aState.PushAbsoluteContainingBlock(nullptr, nullptr, absoluteSaveState);
+ } else if (!(bits & FCDATA_SKIP_ABSPOS_PUSH)) {
+ maybeAbsoluteContainingBlock->AddStateBits(
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (maybeAbsoluteContainingBlockStyleFrame->IsAbsPosContainingBlock()) {
+ auto* cf =
+ static_cast<nsContainerFrame*>(maybeAbsoluteContainingBlock);
+ aState.PushAbsoluteContainingBlock(
+ cf, maybeAbsoluteContainingBlockStyleFrame, absoluteSaveState);
+ }
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(newFrameAsContainer, floatSaveState);
+
+ if (bits & FCDATA_USE_CHILD_ITEMS) {
+ // At this point, we have not set up the auto value for this frame, and
+ // no caller will have set it so it is not redundant and therefor will
+ // not assert.
+ AutoFrameConstructionPageName pageNameTracker(aState,
+ newFrameAsContainer);
+ ConstructFramesFromItemList(
+ aState, aItem.mChildItems, newFrameAsContainer,
+ bits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
+ } else {
+ // Process the child frames.
+ ProcessChildren(aState, content, computedStyle, newFrameAsContainer,
+ !(bits & FCDATA_DISALLOW_GENERATED_CONTENT), childList,
+ (bits & FCDATA_ALLOW_BLOCK_STYLES) != 0,
+ possiblyLeafFrame);
+ }
+
+ if (bits & FCDATA_WRAP_KIDS_IN_BLOCKS) {
+ nsFrameList newList;
+ nsFrameList currentBlockList;
+ nsIFrame* f;
+ while ((f = childList.FirstChild()) != nullptr) {
+ bool wrapFrame = IsInlineFrame(f) || IsFramePartOfIBSplit(f);
+ if (!wrapFrame) {
+ FlushAccumulatedBlock(aState, content, newFrameAsContainer,
+ currentBlockList, newList);
+ }
+
+ childList.RemoveFrame(f);
+ if (wrapFrame) {
+ currentBlockList.AppendFrame(nullptr, f);
+ } else {
+ newList.AppendFrame(nullptr, f);
+ }
+ }
+ FlushAccumulatedBlock(aState, content, newFrameAsContainer,
+ currentBlockList, newList);
+
+ if (childList.NotEmpty()) {
+ // an error must have occurred, delete unprocessed frames
+ DestroyFramesInList(mPresShell, childList);
+ }
+
+ childList = std::move(newList);
+ }
+
+ if (!(bits & FCDATA_ALLOW_GRID_FLEX_COLUMN) ||
+ !MayNeedToCreateColumnSpanSiblings(newFrameAsContainer, childList)) {
+ // Set the frame's initial child list. Note that MathML depends on this
+ // being called even if childList is empty!
+ newFrameAsContainer->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ } else {
+ // Extract any initial non-column-span kids, and put them in inner
+ // frame's child list.
+ nsFrameList initialNonColumnSpanKids =
+ childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+ newFrameAsContainer->SetInitialChildList(
+ FrameChildListID::Principal, std::move(initialNonColumnSpanKids));
+
+ if (childList.NotEmpty()) {
+ nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
+ aState, newFrameAsContainer, childList,
+ // Column content should never be a absolute/fixed positioned
+ // containing block. Pass nullptr as aPositionedFrame.
+ nullptr);
+
+ MOZ_ASSERT(outerFrame,
+ "outerFrame should be non-null if multi-column container "
+ "is created.");
+ FinishBuildingColumns(aState, outerFrame, newFrameAsContainer,
+ columnSpanSiblings);
+ }
+ }
+ }
+ }
+
+ NS_ASSERTION(newFrame->IsLineParticipant() ==
+ ((bits & FCDATA_IS_LINE_PARTICIPANT) != 0),
+ "Incorrectly set FCDATA_IS_LINE_PARTICIPANT bits");
+
+ // Even if mCreatingExtraFrames is set, we may need to SetPrimaryFrame for
+ // generated content that doesn't have one yet. Note that we have to examine
+ // the frame bit, because by this point mIsGeneratedContent has been cleared
+ // on aItem.
+ if ((!aState.mCreatingExtraFrames ||
+ (aItem.mContent->IsRootOfNativeAnonymousSubtree() &&
+ !aItem.mContent->GetPrimaryFrame())) &&
+ !(bits & FCDATA_SKIP_FRAMESET)) {
+ aItem.mContent->SetPrimaryFrame(primaryFrame);
+ ActiveLayerTracker::TransferActivityToFrame(aItem.mContent, primaryFrame);
+ }
+}
+
+static void GatherSubtreeElements(Element* aElement,
+ nsTArray<Element*>& aElements) {
+ aElements.AppendElement(aElement);
+ StyleChildrenIterator iter(aElement);
+ for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
+ if (!c->IsElement()) {
+ continue;
+ }
+ GatherSubtreeElements(c->AsElement(), aElements);
+ }
+}
+
+nsresult nsCSSFrameConstructor::GetAnonymousContent(
+ nsIContent* aParent, nsIFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aContent) {
+ nsIAnonymousContentCreator* creator = do_QueryFrame(aParentFrame);
+ if (!creator) {
+ return NS_OK;
+ }
+
+ nsresult rv = creator->CreateAnonymousContent(aContent);
+ if (NS_FAILED(rv)) {
+ // CreateAnonymousContent failed, e.g. because the page has a <use> loop.
+ return rv;
+ }
+
+ if (aContent.IsEmpty()) {
+ return NS_OK;
+ }
+
+ const bool devtoolsEventsEnabled =
+ mDocument->DevToolsAnonymousAndShadowEventsEnabled();
+
+ MOZ_ASSERT(aParent->IsElement());
+ for (const auto& info : aContent) {
+ // get our child's content and set its parent to our content
+ nsIContent* content = info.mContent;
+ content->SetIsNativeAnonymousRoot();
+
+ BindContext context(*aParent->AsElement(), BindContext::ForNativeAnonymous);
+ rv = content->BindToTree(context, *aParent);
+
+ if (NS_FAILED(rv)) {
+ content->UnbindFromTree();
+ return rv;
+ }
+
+ if (devtoolsEventsEnabled) {
+ content->QueueDevtoolsAnonymousEvent(/* aIsRemove = */ false);
+ }
+ }
+
+ // Some situations where we don't cache anonymous content styles:
+ //
+ // * when visibility or pointer-events is anything other than the initial
+ // value; we rely on visibility and pointer-events inheriting into anonymous
+ // content, but don't bother adding this state to the AnonymousContentKey,
+ // since it's not so common. Note that with overlay scrollbars, scrollbars
+ // always start off with pointer-events: none so we don't need to check for
+ // that in that case.
+ //
+ // * when the medium is anything other than screen; some UA style sheet rules
+ // apply in e.g. print medium, and will give different results from the
+ // cached styles
+ Maybe<bool> computedAllowStyleCaching;
+ auto ComputeAllowStyleCaching = [&] {
+ if (!StaticPrefs::layout_css_cached_scrollbar_styles_enabled()) {
+ return false;
+ }
+ if (aParentFrame->StyleVisibility()->mVisible != StyleVisibility::Visible) {
+ return false;
+ }
+ nsPresContext* pc = mPresShell->GetPresContext();
+ if (!pc->UseOverlayScrollbars() &&
+ aParentFrame->StyleUI()->ComputedPointerEvents() !=
+ StylePointerEvents::Auto) {
+ return false;
+ }
+ if (pc->Medium() != nsGkAtoms::screen) {
+ return false;
+ }
+ return true;
+ };
+
+ auto AllowStyleCaching = [&] {
+ if (computedAllowStyleCaching.isNothing()) {
+ computedAllowStyleCaching.emplace(ComputeAllowStyleCaching());
+ }
+ return computedAllowStyleCaching.value();
+ };
+
+ // Compute styles for the anonymous content tree.
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ for (auto& info : aContent) {
+ Element* e = Element::FromNode(info.mContent);
+ if (!e) {
+ continue;
+ }
+
+ if (info.mKey == AnonymousContentKey::None || !AllowStyleCaching()) {
+ // Most NAC subtrees do not use caching of computed styles. Just go
+ // ahead and eagerly style the subtree.
+ styleSet->StyleNewSubtree(e);
+ continue;
+ }
+
+ // We have a NAC subtree for which we can use cached styles.
+ AutoTArray<RefPtr<ComputedStyle>, 2> cachedStyles;
+ AutoTArray<Element*, 2> elements;
+
+ GatherSubtreeElements(e, elements);
+ styleSet->GetCachedAnonymousContentStyles(info.mKey, cachedStyles);
+
+ if (cachedStyles.IsEmpty()) {
+ // We haven't stored cached styles for this kind of NAC subtree yet.
+ // Eagerly compute those styles, then cache them for later.
+ styleSet->StyleNewSubtree(e);
+ for (Element* e : elements) {
+ if (e->HasServoData()) {
+ cachedStyles.AppendElement(ServoStyleSet::ResolveServoStyle(*e));
+ } else {
+ cachedStyles.AppendElement(nullptr);
+ }
+ }
+ styleSet->PutCachedAnonymousContentStyles(info.mKey,
+ std::move(cachedStyles));
+ continue;
+ }
+
+ // We previously stored cached styles for this kind of NAC subtree.
+ // Iterate over them and set them on the subtree's elements.
+ MOZ_ASSERT(cachedStyles.Length() == elements.Length(),
+ "should always produce the same size NAC subtree");
+ for (size_t i = 0, len = cachedStyles.Length(); i != len; ++i) {
+ if (cachedStyles[i]) {
+#ifdef DEBUG
+ // Assert that our cached style is the same as one we could compute.
+ RefPtr<ComputedStyle> cs = styleSet->ResolveStyleLazily(*elements[i]);
+ MOZ_ASSERT(
+ cachedStyles[i]->EqualForCachedAnonymousContentStyle(*cs),
+ "cached anonymous content styles should be identical to those we "
+ "would compute normally");
+ // All overlay scrollbars start off as inactive, so we can rely on their
+ // pointer-events value being always none.
+ MOZ_ASSERT(!mPresShell->GetPresContext()->UseOverlayScrollbars() ||
+ cs->StyleUI()->ComputedPointerEvents() ==
+ StylePointerEvents::None);
+#endif
+ Servo_SetExplicitStyle(elements[i], cachedStyles[i]);
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+// XUL frames are not allowed to be out of flow.
+#define SIMPLE_XUL_FCDATA(_func) \
+ FrameConstructionData(_func, \
+ FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH)
+#define SCROLLABLE_XUL_FCDATA(_func) \
+ FrameConstructionData(_func, FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_SKIP_ABSPOS_PUSH | \
+ FCDATA_MAY_NEED_SCROLLFRAME)
+// .. but we allow some XUL frames to be _containers_ for out-of-flow content
+// (This is the same as SCROLLABLE_XUL_FCDATA, but w/o FCDATA_SKIP_ABSPOS_PUSH)
+#define SCROLLABLE_ABSPOS_CONTAINER_XUL_FCDATA(_func) \
+ FrameConstructionData( \
+ _func, FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_MAY_NEED_SCROLLFRAME)
+
+#define SIMPLE_XUL_CREATE(_tag, _func) \
+ { nsGkAtoms::_tag, SIMPLE_XUL_FCDATA(_func) }
+#define SCROLLABLE_XUL_CREATE(_tag, _func) \
+ { nsGkAtoms::_tag, SCROLLABLE_XUL_FCDATA(_func) }
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULTagData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ MOZ_ASSERT(aElement.IsXULElement());
+ static constexpr FrameConstructionData kPopupData(
+ NS_NewMenuPopupFrame, FCDATA_IS_POPUP | FCDATA_SKIP_ABSPOS_PUSH);
+
+ static constexpr FrameConstructionDataByTag sXULTagData[] = {
+ SIMPLE_XUL_CREATE(image, NS_NewXULImageFrame),
+ SIMPLE_XUL_CREATE(treechildren, NS_NewTreeBodyFrame),
+ SIMPLE_TAG_CHAIN(label,
+ nsCSSFrameConstructor::FindXULLabelOrDescriptionData),
+ SIMPLE_TAG_CHAIN(description,
+ nsCSSFrameConstructor::FindXULLabelOrDescriptionData),
+#ifdef XP_MACOSX
+ SIMPLE_TAG_CHAIN(menubar, nsCSSFrameConstructor::FindXULMenubarData),
+#endif /* XP_MACOSX */
+ SIMPLE_XUL_CREATE(iframe, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(editor, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(browser, NS_NewSubDocumentFrame),
+ SIMPLE_XUL_CREATE(splitter, NS_NewSplitterFrame),
+ SIMPLE_XUL_CREATE(scrollbar, NS_NewScrollbarFrame),
+ SIMPLE_XUL_CREATE(slider, NS_NewSliderFrame),
+ SIMPLE_XUL_CREATE(thumb, NS_NewSimpleXULLeafFrame),
+ SIMPLE_XUL_CREATE(scrollcorner, NS_NewSimpleXULLeafFrame),
+ SIMPLE_XUL_CREATE(resizer, NS_NewSimpleXULLeafFrame),
+ SIMPLE_XUL_CREATE(scrollbarbutton, NS_NewScrollbarButtonFrame),
+ {nsGkAtoms::panel, kPopupData},
+ {nsGkAtoms::menupopup, kPopupData},
+ {nsGkAtoms::tooltip, kPopupData},
+ };
+
+ return FindDataByTag(aElement, aStyle, sXULTagData, ArrayLength(sXULTagData));
+}
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULLabelOrDescriptionData(const Element& aElement,
+ ComputedStyle&) {
+ // Follow CSS display value if no value attribute
+ if (!aElement.HasAttr(nsGkAtoms::value)) {
+ return nullptr;
+ }
+
+ // Follow CSS display if there's no crop="center".
+ if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::crop,
+ nsGkAtoms::center, eCaseMatters)) {
+ return nullptr;
+ }
+
+ static constexpr FrameConstructionData sMiddleCroppingData =
+ SIMPLE_XUL_FCDATA(NS_NewMiddleCroppingLabelFrame);
+ return &sMiddleCroppingData;
+}
+
+#ifdef XP_MACOSX
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindXULMenubarData(const Element& aElement,
+ ComputedStyle&) {
+ if (aElement.OwnerDoc()->IsInChromeDocShell()) {
+ BrowsingContext* bc = aElement.OwnerDoc()->GetBrowsingContext();
+ bool isRoot = bc && !bc->GetParent();
+ if (isRoot) {
+ // This is the root. Suppress the menubar, since on Mac
+ // window menus are not attached to the window.
+ static constexpr FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
+ return &sSuppressData;
+ }
+ }
+
+ return nullptr;
+}
+#endif /* XP_MACOSX */
+
+already_AddRefed<ComputedStyle> nsCSSFrameConstructor::BeginBuildingScrollFrame(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ ComputedStyle* aContentStyle, nsContainerFrame* aParentFrame,
+ PseudoStyleType aScrolledPseudo, bool aIsRoot,
+ nsContainerFrame*& aNewFrame) {
+ nsContainerFrame* gfxScrollFrame = aNewFrame;
+
+ if (!gfxScrollFrame) {
+ gfxScrollFrame = NS_NewHTMLScrollFrame(mPresShell, aContentStyle, aIsRoot);
+ InitAndRestoreFrame(aState, aContent, aParentFrame, gfxScrollFrame);
+ }
+
+ MOZ_ASSERT(gfxScrollFrame);
+
+ // if there are any anonymous children for the scroll frame, create
+ // frames for them.
+ //
+ // We can't take the normal ProcessChildren path, because the NAC needs to
+ // be parented to the scrollframe, and everything else needs to be parented
+ // to the scrolledframe.
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> scrollNAC;
+ DebugOnly<nsresult> rv =
+ GetAnonymousContent(aContent, gfxScrollFrame, scrollNAC);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ nsFrameList anonymousList;
+ if (!scrollNAC.IsEmpty()) {
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(gfxScrollFrame, floatSaveState);
+
+ AutoFrameConstructionItemList items(this);
+ AutoFrameConstructionPageName pageNameTracker(aState, gfxScrollFrame);
+ AddFCItemsForAnonymousContent(aState, gfxScrollFrame, scrollNAC, items,
+ pageNameTracker);
+ ConstructFramesFromItemList(aState, items, gfxScrollFrame,
+ /* aParentIsWrapperAnonBox = */ false,
+ anonymousList);
+ }
+
+ aNewFrame = gfxScrollFrame;
+ gfxScrollFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // we used the style that was passed in. So resolve another one.
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ RefPtr<ComputedStyle> scrolledChildStyle =
+ styleSet->ResolveInheritingAnonymousBoxStyle(aScrolledPseudo,
+ aContentStyle);
+
+ gfxScrollFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(anonymousList));
+
+ return scrolledChildStyle.forget();
+}
+
+void nsCSSFrameConstructor::FinishBuildingScrollFrame(
+ nsContainerFrame* aScrollFrame, nsIFrame* aScrolledFrame) {
+ aScrollFrame->AppendFrames(FrameChildListID::Principal,
+ nsFrameList(aScrolledFrame, aScrolledFrame));
+}
+
+/**
+ * Called to wrap a gfx scrollframe around a frame. The hierarchy will look like
+ * this
+ *
+ * ------- for gfx scrollbars ------
+ *
+ *
+ * ScrollFrame
+ * ^
+ * |
+ * Frame (scrolled frame you passed in)
+ *
+ *
+ * -----------------------------------
+ * LEGEND:
+ *
+ * ScrollFrame: This is a frame that manages gfx cross platform frame based
+ * scrollbars.
+ *
+ * @param aContent the content node of the child to wrap.
+ *
+ * @param aScrolledFrame The frame of the content to wrap. This should not be
+ * Initialized. This method will initialize it with a scrolled pseudo and no
+ * nsIContent. The content will be attached to the scrollframe returned.
+ *
+ * @param aContentStyle the style that has already been resolved for the content
+ * being passed in.
+ *
+ * @param aParentFrame The parent to attach the scroll frame to
+ *
+ * @param aNewFrame The new scrollframe or gfx scrollframe that we create. It
+ * will contain the scrolled frame you passed in. (returned) If this is not
+ * null, we'll just use it
+ *
+ * @param aScrolledContentStyle the style that was resolved for the scrolled
+ * frame. (returned)
+ */
+void nsCSSFrameConstructor::BuildScrollFrame(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ ComputedStyle* aContentStyle,
+ nsIFrame* aScrolledFrame,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame*& aNewFrame) {
+ RefPtr<ComputedStyle> scrolledContentStyle = BeginBuildingScrollFrame(
+ aState, aContent, aContentStyle, aParentFrame,
+ PseudoStyleType::scrolledContent, false, aNewFrame);
+
+ aScrolledFrame->SetComputedStyleWithoutNotification(scrolledContentStyle);
+ InitAndRestoreFrame(aState, aContent, aNewFrame, aScrolledFrame);
+
+ FinishBuildingScrollFrame(aNewFrame, aScrolledFrame);
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDisplayData(const nsStyleDisplay& aDisplay,
+ const Element& aElement) {
+ static_assert(eParentTypeCount < (1 << (32 - FCDATA_PARENT_TYPE_OFFSET)),
+ "Check eParentTypeCount should not overflow");
+
+ // The style system ensures that floated and positioned frames are
+ // block-level.
+ NS_ASSERTION(
+ !(aDisplay.IsFloatingStyle() || aDisplay.IsAbsolutelyPositionedStyle()) ||
+ aDisplay.IsBlockOutsideStyle(),
+ "Style system did not apply CSS2.1 section 9.7 fixups");
+
+ // If this is "body", try propagating its scroll style to the viewport
+ // Note that we need to do this even if the body is NOT scrollable;
+ // it might have dynamically changed from scrollable to not scrollable,
+ // and that might need to be propagated.
+ // XXXbz is this the right place to do this? If this code moves,
+ // make this function static.
+ bool propagatedScrollToViewport = false;
+ if (aElement.IsHTMLElement(nsGkAtoms::body)) {
+ if (nsPresContext* presContext = mPresShell->GetPresContext()) {
+ propagatedScrollToViewport =
+ presContext->UpdateViewportScrollStylesOverride() == &aElement;
+ MOZ_ASSERT(!propagatedScrollToViewport ||
+ !mPresShell->GetPresContext()->IsPaginated(),
+ "Shouldn't propagate scroll in paginated contexts");
+ }
+ }
+
+ switch (aDisplay.DisplayInside()) {
+ case StyleDisplayInside::Flow:
+ case StyleDisplayInside::FlowRoot: {
+ if (aDisplay.IsInlineFlow()) {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructInline,
+ FCDATA_IS_INLINE | FCDATA_IS_LINE_PARTICIPANT);
+ return &data;
+ }
+
+ // If the frame is a block-level frame and is scrollable, then wrap it in
+ // a scroll frame. Except we don't want to do that for paginated contexts
+ // for frames that are block-outside and aren't frames for native
+ // anonymous stuff.
+ // XXX Ignore tables for the time being (except caption)
+ const uint32_t kCaptionCtorFlags =
+ FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable);
+ bool caption = aDisplay.mDisplay == StyleDisplay::TableCaption;
+ bool suppressScrollFrame = false;
+ bool needScrollFrame =
+ aDisplay.IsScrollableOverflow() && !propagatedScrollToViewport;
+ if (needScrollFrame) {
+ suppressScrollFrame = mPresShell->GetPresContext()->IsPaginated() &&
+ aDisplay.IsBlockOutsideStyle() &&
+ !aElement.IsInNativeAnonymousSubtree();
+ if (!suppressScrollFrame) {
+ static constexpr FrameConstructionData sScrollableBlockData[2] = {
+ {&nsCSSFrameConstructor::ConstructScrollableBlock},
+ {&nsCSSFrameConstructor::ConstructScrollableBlock,
+ kCaptionCtorFlags}};
+ return &sScrollableBlockData[caption];
+ }
+
+ // If the scrollable frame would have propagated its scrolling to the
+ // viewport, we still want to construct a regular block rather than a
+ // scrollframe so that it paginates correctly, but we don't want to set
+ // the bit on the block that tells it to clip at paint time.
+ if (mPresShell->GetPresContext()->ElementWouldPropagateScrollStyles(
+ aElement)) {
+ suppressScrollFrame = false;
+ }
+ }
+
+ // Handle various non-scrollable blocks.
+ static constexpr FrameConstructionData sNonScrollableBlockData[2][2] = {
+ {{&nsCSSFrameConstructor::ConstructNonScrollableBlock},
+ {&nsCSSFrameConstructor::ConstructNonScrollableBlock,
+ kCaptionCtorFlags}},
+ {{&nsCSSFrameConstructor::ConstructNonScrollableBlock,
+ FCDATA_FORCED_NON_SCROLLABLE_BLOCK},
+ {&nsCSSFrameConstructor::ConstructNonScrollableBlock,
+ FCDATA_FORCED_NON_SCROLLABLE_BLOCK | kCaptionCtorFlags}}};
+ return &sNonScrollableBlockData[suppressScrollFrame][caption];
+ }
+ case StyleDisplayInside::Table: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTable);
+ return &data;
+ }
+ // NOTE: In the unlikely event that we add another table-part here that
+ // has a desired-parent-type (& hence triggers table fixup), we'll need to
+ // also update the flexbox chunk in ComputedStyle::ApplyStyleFixups().
+ case StyleDisplayInside::TableRowGroup: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
+ return &data;
+ }
+ case StyleDisplayInside::TableColumn: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableCol,
+ FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeColGroup));
+ return &data;
+ }
+ case StyleDisplayInside::TableColumnGroup: {
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewTableColGroupFrame),
+ FCDATA_IS_TABLE_PART | FCDATA_DISALLOW_OUT_OF_FLOW |
+ FCDATA_SKIP_ABSPOS_PUSH |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
+ return &data;
+ }
+ case StyleDisplayInside::TableHeaderGroup: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
+ return &data;
+ }
+ case StyleDisplayInside::TableFooterGroup: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable));
+ return &data;
+ }
+ case StyleDisplayInside::TableRow: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRowGroup));
+ return &data;
+ }
+ case StyleDisplayInside::TableCell: {
+ static constexpr FrameConstructionData data(
+ &nsCSSFrameConstructor::ConstructTableCell,
+ FCDATA_IS_TABLE_PART | FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRow));
+ return &data;
+ }
+ case StyleDisplayInside::Flex:
+ case StyleDisplayInside::WebkitBox: {
+ static constexpr FrameConstructionData nonScrollableData(
+ ToCreationFunc(NS_NewFlexContainerFrame));
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewFlexContainerFrame),
+ FCDATA_MAY_NEED_SCROLLFRAME);
+ return MOZ_UNLIKELY(propagatedScrollToViewport) ? &nonScrollableData
+ : &data;
+ }
+ case StyleDisplayInside::Grid: {
+ static constexpr FrameConstructionData nonScrollableData(
+ ToCreationFunc(NS_NewGridContainerFrame));
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewGridContainerFrame),
+ FCDATA_MAY_NEED_SCROLLFRAME);
+ return MOZ_UNLIKELY(propagatedScrollToViewport) ? &nonScrollableData
+ : &data;
+ }
+ case StyleDisplayInside::Ruby: {
+ static constexpr FrameConstructionData data[] = {
+ {&nsCSSFrameConstructor::ConstructBlockRubyFrame,
+ FCDATA_MAY_NEED_SCROLLFRAME},
+ {ToCreationFunc(NS_NewRubyFrame), FCDATA_IS_LINE_PARTICIPANT}};
+ bool isInline = aDisplay.DisplayOutside() == StyleDisplayOutside::Inline;
+ return &data[isInline];
+ }
+ case StyleDisplayInside::RubyBase: {
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewRubyBaseFrame),
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer));
+ return &data;
+ }
+ case StyleDisplayInside::RubyBaseContainer: {
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewRubyBaseContainerFrame),
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby));
+ return &data;
+ }
+ case StyleDisplayInside::RubyText: {
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewRubyTextFrame),
+ FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyTextContainer));
+ return &data;
+ }
+ case StyleDisplayInside::RubyTextContainer: {
+ static constexpr FrameConstructionData data(
+ ToCreationFunc(NS_NewRubyTextContainerFrame),
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby));
+ return &data;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown 'display' value");
+ return nullptr;
+ }
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructScrollableBlock(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsContainerFrame* newFrame = nullptr;
+ RefPtr<ComputedStyle> scrolledContentStyle = BeginBuildingScrollFrame(
+ aState, content, computedStyle,
+ aState.GetGeometricParent(*aDisplay, aParentFrame),
+ PseudoStyleType::scrolledContent, false, newFrame);
+
+ // Create our block frame
+ // pass a temporary stylecontext, the correct one will be set later
+ nsContainerFrame* scrolledFrame =
+ NS_NewBlockFormattingContext(mPresShell, computedStyle);
+
+ // Make sure to AddChild before we call ConstructBlock so that we
+ // end up before our descendants in fixed-pos lists as needed.
+ aState.AddChild(newFrame, aFrameList, content, aParentFrame);
+
+ nsFrameList blockList;
+ ConstructBlock(aState, content, newFrame, newFrame, scrolledContentStyle,
+ &scrolledFrame, blockList,
+ newFrame->IsAbsPosContainingBlock() ? newFrame : nullptr);
+
+ MOZ_ASSERT(blockList.OnlyChild() == scrolledFrame,
+ "Scrollframe's frameList should be exactly the scrolled frame!");
+ FinishBuildingScrollFrame(newFrame, scrolledFrame);
+
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructNonScrollableBlock(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ // We want a block formatting context root in paginated contexts for
+ // every block that would be scrollable in a non-paginated context.
+ // We mark our blocks with a bit here if this condition is true, so
+ // we can check it later in nsIFrame::ApplyPaginatedOverflowClipping.
+ bool clipPaginatedOverflow =
+ (aItem.mFCData->mBits & FCDATA_FORCED_NON_SCROLLABLE_BLOCK) != 0;
+ nsFrameState flags = nsFrameState(0);
+ if ((aDisplay->IsAbsolutelyPositionedStyle() || aDisplay->IsFloatingStyle() ||
+ aDisplay->DisplayInside() == StyleDisplayInside::FlowRoot ||
+ clipPaginatedOverflow) &&
+ !aParentFrame->IsInSVGTextSubtree()) {
+ flags = NS_BLOCK_STATIC_BFC;
+ if (clipPaginatedOverflow) {
+ flags |= NS_BLOCK_CLIP_PAGINATED_OVERFLOW;
+ }
+ }
+
+ nsContainerFrame* newFrame = NS_NewBlockFrame(mPresShell, computedStyle);
+ newFrame->AddStateBits(flags);
+ ConstructBlock(aState, aItem.mContent,
+ aState.GetGeometricParent(*aDisplay, aParentFrame),
+ aParentFrame, computedStyle, &newFrame, aFrameList,
+ newFrame->IsAbsPosContainingBlock() ? newFrame : nullptr);
+ return newFrame;
+}
+
+void nsCSSFrameConstructor::InitAndRestoreFrame(
+ const nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, nsIFrame* aNewFrame, bool aAllowCounters) {
+ MOZ_ASSERT(aNewFrame, "Null frame cannot be initialized");
+
+ // Initialize the frame
+ aNewFrame->Init(aContent, aParentFrame, nullptr);
+ aNewFrame->AddStateBits(aState.mAdditionalStateBits);
+
+ if (aState.mFrameState) {
+ // Restore frame state for just the newly created frame.
+ RestoreFrameStateFor(aNewFrame, aState.mFrameState);
+ }
+
+ if (aAllowCounters &&
+ mContainStyleScopeManager.AddCounterChanges(aNewFrame)) {
+ CountersDirty();
+ }
+}
+
+already_AddRefed<ComputedStyle> nsCSSFrameConstructor::ResolveComputedStyle(
+ nsIContent* aContent) {
+ if (auto* element = Element::FromNode(aContent)) {
+ return ServoStyleSet::ResolveServoStyle(*element);
+ }
+
+ MOZ_ASSERT(aContent->IsText(),
+ "shouldn't waste time creating ComputedStyles for "
+ "comments and processing instructions");
+
+ Element* parent = aContent->GetFlattenedTreeParentElement();
+ MOZ_ASSERT(parent, "Text out of the flattened tree?");
+
+ // FIXME(emilio): The const_cast is unfortunate, but it's not worse than what
+ // we did before.
+ //
+ // We could use ResolveServoStyle, but that would involve extra unnecessary
+ // refcount traffic...
+ auto* parentStyle =
+ const_cast<ComputedStyle*>(Servo_Element_GetMaybeOutOfDateStyle(parent));
+ MOZ_ASSERT(parentStyle,
+ "How are we inserting text frames in an unstyled element?");
+ return mPresShell->StyleSet()->ResolveStyleForText(aContent, parentStyle);
+}
+
+// MathML Mod - RBS
+void nsCSSFrameConstructor::FlushAccumulatedBlock(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, nsFrameList& aBlockList,
+ nsFrameList& aNewList) {
+ if (aBlockList.IsEmpty()) {
+ // Nothing to do
+ return;
+ }
+
+ auto anonPseudo = PseudoStyleType::mozMathMLAnonymousBlock;
+
+ ComputedStyle* parentContext =
+ nsIFrame::CorrectStyleParentFrame(aParentFrame, anonPseudo)->Style();
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ RefPtr<ComputedStyle> blockContext =
+ styleSet->ResolveInheritingAnonymousBoxStyle(anonPseudo, parentContext);
+
+ // then, create a block frame that will wrap the child frames. Make it a
+ // MathML frame so that Get(Absolute/Float)ContainingBlockFor know that this
+ // is not a suitable block.
+ nsContainerFrame* blockFrame =
+ NS_NewMathMLmathBlockFrame(mPresShell, blockContext);
+
+ InitAndRestoreFrame(aState, aContent, aParentFrame, blockFrame);
+ ReparentFrames(this, blockFrame, aBlockList, false);
+ // We have to walk over aBlockList before we hand it over to blockFrame.
+ for (nsIFrame* f : aBlockList) {
+ f->SetParentIsWrapperAnonBox();
+ }
+ // abs-pos and floats are disabled in MathML children so we don't have to
+ // worry about messing up those.
+ blockFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(aBlockList));
+ aNewList.AppendFrame(nullptr, blockFrame);
+}
+
+// Only <math> elements can be floated or positioned. All other MathML
+// should be in-flow.
+#define SIMPLE_MATHML_CREATE(_tag, _func) \
+ { \
+ nsGkAtoms::_tag, { \
+ _func, FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_FORCE_NULL_ABSPOS_CONTAINER | \
+ FCDATA_WRAP_KIDS_IN_BLOCKS \
+ } \
+ }
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindMathMLData(const Element& aElement,
+ ComputedStyle& aStyle) {
+ MOZ_ASSERT(aElement.IsMathMLElement());
+
+ nsAtom* tag = aElement.NodeInfo()->NameAtom();
+
+ // Handle <math> specially, because it sometimes produces inlines
+ if (tag == nsGkAtoms::math) {
+ // The IsBlockOutsideStyle() check must match what
+ // specified::Display::equivalent_block_display is checking for
+ // already-block-outside things. Though the behavior here for the
+ // display:table case is pretty weird...
+ if (aStyle.StyleDisplay()->IsBlockOutsideStyle()) {
+ static constexpr FrameConstructionData sBlockMathData(
+ ToCreationFunc(NS_NewMathMLmathBlockFrame),
+ FCDATA_FORCE_NULL_ABSPOS_CONTAINER | FCDATA_WRAP_KIDS_IN_BLOCKS);
+ return &sBlockMathData;
+ }
+
+ static constexpr FrameConstructionData sInlineMathData(
+ ToCreationFunc(NS_NewMathMLmathInlineFrame),
+ FCDATA_FORCE_NULL_ABSPOS_CONTAINER | FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_WRAP_KIDS_IN_BLOCKS);
+ return &sInlineMathData;
+ }
+
+ static constexpr FrameConstructionDataByTag sMathMLData[] = {
+ SIMPLE_MATHML_CREATE(annotation_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(annotation_xml_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mi_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mn_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(ms_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mtext_, NS_NewMathMLTokenFrame),
+ SIMPLE_MATHML_CREATE(mo_, NS_NewMathMLmoFrame),
+ SIMPLE_MATHML_CREATE(mfrac_, NS_NewMathMLmfracFrame),
+ SIMPLE_MATHML_CREATE(msup_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(msub_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(msubsup_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(munder_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(mover_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(munderover_, NS_NewMathMLmunderoverFrame),
+ SIMPLE_MATHML_CREATE(mphantom_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mpadded_, NS_NewMathMLmpaddedFrame),
+ SIMPLE_MATHML_CREATE(mspace_, NS_NewMathMLmspaceFrame),
+ SIMPLE_MATHML_CREATE(none, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mprescripts_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mfenced_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mmultiscripts_, NS_NewMathMLmmultiscriptsFrame),
+ SIMPLE_MATHML_CREATE(mstyle_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(msqrt_, NS_NewMathMLmsqrtFrame),
+ SIMPLE_MATHML_CREATE(mroot_, NS_NewMathMLmrootFrame),
+ SIMPLE_MATHML_CREATE(maction_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(mrow_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(merror_, NS_NewMathMLmrowFrame),
+ SIMPLE_MATHML_CREATE(menclose_, NS_NewMathMLmencloseFrame),
+ SIMPLE_MATHML_CREATE(semantics_, NS_NewMathMLmrowFrame)};
+
+ return FindDataByTag(aElement, aStyle, sMathMLData, ArrayLength(sMathMLData));
+}
+
+nsContainerFrame* nsCSSFrameConstructor::ConstructFrameWithAnonymousChild(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, nsFrameList& aFrameList,
+ ContainerFrameCreationFunc aConstructor,
+ ContainerFrameCreationFunc aInnerConstructor, PseudoStyleType aInnerPseudo,
+ bool aCandidateRootFrame) {
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ // Create the outer frame:
+ nsContainerFrame* newFrame = aConstructor(mPresShell, computedStyle);
+
+ InitAndRestoreFrame(aState, content,
+ aCandidateRootFrame
+ ? aState.GetGeometricParent(
+ *computedStyle->StyleDisplay(), aParentFrame)
+ : aParentFrame,
+ newFrame);
+ newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+
+ // Create the pseudo SC for the anonymous wrapper child as a child of the SC:
+ RefPtr<ComputedStyle> scForAnon =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(aInnerPseudo,
+ computedStyle);
+
+ // Create the anonymous inner wrapper frame
+ nsContainerFrame* innerFrame = aInnerConstructor(mPresShell, scForAnon);
+
+ InitAndRestoreFrame(aState, content, newFrame, innerFrame);
+
+ // Put the newly created frames into the right child list
+ SetInitialSingleChild(newFrame, innerFrame);
+
+ aState.AddChild(newFrame, aFrameList, content, aParentFrame,
+ aCandidateRootFrame, aCandidateRootFrame);
+
+ if (!mRootElementFrame && aCandidateRootFrame) {
+ mRootElementFrame = newFrame;
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(innerFrame, floatSaveState);
+
+ nsFrameList childList;
+
+ // Process children
+ if (aItem.mFCData->mBits & FCDATA_USE_CHILD_ITEMS) {
+ ConstructFramesFromItemList(
+ aState, aItem.mChildItems, innerFrame,
+ aItem.mFCData->mBits & FCDATA_IS_WRAPPER_ANON_BOX, childList);
+ } else {
+ ProcessChildren(aState, content, computedStyle, innerFrame, true, childList,
+ false);
+ }
+
+ // Set the inner wrapper frame's initial primary list
+ innerFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructOuterSVG(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ return ConstructFrameWithAnonymousChild(
+ aState, aItem, aParentFrame, aFrameList, NS_NewSVGOuterSVGFrame,
+ NS_NewSVGOuterSVGAnonChildFrame, PseudoStyleType::mozSVGOuterSVGAnonChild,
+ true);
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructMarker(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ return ConstructFrameWithAnonymousChild(
+ aState, aItem, aParentFrame, aFrameList, NS_NewSVGMarkerFrame,
+ NS_NewSVGMarkerAnonChildFrame, PseudoStyleType::mozSVGMarkerAnonChild,
+ false);
+}
+
+// Only outer <svg> elements can be floated or positioned. All other SVG
+// should be in-flow.
+#define SIMPLE_SVG_FCDATA(_func) \
+ FrameConstructionData(ToCreationFunc(_func), \
+ FCDATA_DISALLOW_OUT_OF_FLOW | \
+ FCDATA_SKIP_ABSPOS_PUSH | \
+ FCDATA_DISALLOW_GENERATED_CONTENT)
+#define SIMPLE_SVG_CREATE(_tag, _func) \
+ { nsGkAtoms::_tag, SIMPLE_SVG_FCDATA(_func) }
+
+/* static */
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindSVGData(const Element& aElement,
+ nsIFrame* aParentFrame,
+ bool aIsWithinSVGText,
+ bool aAllowsTextPathChild,
+ ComputedStyle& aStyle) {
+ MOZ_ASSERT(aElement.IsSVGElement());
+
+ static constexpr FrameConstructionData sSuppressData = SUPPRESS_FCDATA();
+ static constexpr FrameConstructionData sContainerData =
+ SIMPLE_SVG_FCDATA(NS_NewSVGContainerFrame);
+
+ bool parentIsSVG = aIsWithinSVGText;
+ nsIContent* parentContent =
+ aParentFrame ? aParentFrame->GetContent() : nullptr;
+
+ nsAtom* tag = aElement.NodeInfo()->NameAtom();
+
+ // XXXbz should this really be based on the tag of the parent frame's content?
+ // Should it not be based on the type of the parent frame (e.g. whether it's
+ // an SVG frame)?
+ if (parentContent) {
+ // It's not clear whether the SVG spec intends to allow any SVG
+ // content within svg:foreignObject at all (SVG 1.1, section
+ // 23.2), but if it does, it better be svg:svg. So given that
+ // we're allowing it, treat it as a non-SVG parent.
+ parentIsSVG =
+ parentContent->IsSVGElement() &&
+ parentContent->NodeInfo()->NameAtom() != nsGkAtoms::foreignObject;
+ }
+
+ if ((tag != nsGkAtoms::svg && !parentIsSVG) ||
+ (tag == nsGkAtoms::desc || tag == nsGkAtoms::title ||
+ tag == nsGkAtoms::metadata)) {
+ // Sections 5.1 and G.4 of SVG 1.1 say that SVG elements other than
+ // svg:svg not contained within svg:svg are incorrect, although they
+ // don't seem to specify error handling. Ignore them, since many of
+ // our frame classes can't deal. It *may* be that the document
+ // should at that point be considered in error according to F.2, but
+ // it's hard to tell.
+ //
+ // Style mutation can't change this situation, so don't bother
+ // adding to the undisplayed content map.
+ //
+ // We don't currently handle any UI for desc/title/metadata
+ return &sSuppressData;
+ }
+
+ // We don't need frames for animation elements
+ if (aElement.IsSVGAnimationElement()) {
+ return &sSuppressData;
+ }
+
+ if (tag == nsGkAtoms::svg && !parentIsSVG) {
+ // We need outer <svg> elements to have an SVGOuterSVGFrame regardless
+ // of whether they fail conditional processing attributes, since various
+ // SVG frames assume that one exists. We handle the non-rendering
+ // of failing outer <svg> element contents like <switch> statements,
+ // and do the PassesConditionalProcessingTests call in
+ // SVGOuterSVGFrame::Init.
+ static constexpr FrameConstructionData sOuterSVGData(
+ &nsCSSFrameConstructor::ConstructOuterSVG);
+ return &sOuterSVGData;
+ }
+
+ if (tag == nsGkAtoms::marker) {
+ static constexpr FrameConstructionData sMarkerSVGData(
+ &nsCSSFrameConstructor::ConstructMarker);
+ return &sMarkerSVGData;
+ }
+
+ if (!aElement.PassesConditionalProcessingTests()) {
+ // Elements with failing conditional processing attributes never get
+ // rendered. Note that this is not where we select which frame in a
+ // <switch> to render! That happens in SVGSwitchFrame::PaintSVG.
+ if (aIsWithinSVGText) {
+ // SVGTextFrame doesn't handle conditional processing attributes,
+ // so don't create frames for descendants of <text> with failing
+ // attributes. We need frames not to be created so that text layout
+ // is correct.
+ return &sSuppressData;
+ }
+ // If we're not inside <text>, create an SVGContainerFrame (which is a
+ // frame that doesn't render) so that paint servers can still be referenced,
+ // even if they live inside an element with failing conditional processing
+ // attributes.
+ return &sContainerData;
+ }
+
+ // Ensure that a stop frame is a child of a gradient and that gradients
+ // can only have stop children.
+ bool parentIsGradient = aParentFrame && static_cast<SVGGradientFrame*>(
+ do_QueryFrame(aParentFrame));
+ bool stop = (tag == nsGkAtoms::stop);
+ if ((parentIsGradient && !stop) || (!parentIsGradient && stop)) {
+ return &sSuppressData;
+ }
+
+ // Prevent bad frame types being children of filters or parents of filter
+ // primitives. If aParentFrame is null, we know that the frame that will
+ // be created will be an nsInlineFrame, so it can never be a filter.
+ bool parentIsFilter = aParentFrame && aParentFrame->IsSVGFilterFrame();
+ if ((parentIsFilter && !aElement.IsSVGFilterPrimitiveElement()) ||
+ (!parentIsFilter && aElement.IsSVGFilterPrimitiveElement())) {
+ return &sSuppressData;
+ }
+
+ // Prevent bad frame types being children of filter primitives or parents of
+ // filter primitive children. If aParentFrame is null, we know that the frame
+ // that will be created will be an nsInlineFrame, so it can never be a filter
+ // primitive.
+ bool parentIsFEContainerFrame =
+ aParentFrame && aParentFrame->IsSVGFEContainerFrame();
+ if ((parentIsFEContainerFrame &&
+ !aElement.IsSVGFilterPrimitiveChildElement()) ||
+ (!parentIsFEContainerFrame &&
+ aElement.IsSVGFilterPrimitiveChildElement())) {
+ return &sSuppressData;
+ }
+
+ // Special cases for text/tspan/textPath, because the kind of frame
+ // they get depends on the parent frame. We ignore 'a' elements when
+ // determining the parent, however.
+ if (aIsWithinSVGText) {
+ // If aIsWithinSVGText is true, then we know that the "SVG text uses
+ // CSS frames" pref was true when this SVG fragment was first constructed.
+ //
+ // FIXME(bug 1588477) Don't render stuff in display: contents / Shadow DOM
+ // subtrees, because TextCorrespondenceRecorder in the SVG text code doesn't
+ // really know how to deal with it. This kinda sucks. :(
+ if (aParentFrame && aParentFrame->GetContent() != aElement.GetParent()) {
+ return &sSuppressData;
+ }
+
+ // We don't use ConstructInline because we want different behavior
+ // for generated content.
+ static constexpr FrameConstructionData sTSpanData(
+ ToCreationFunc(NS_NewInlineFrame),
+ FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_SKIP_ABSPOS_PUSH |
+ FCDATA_DISALLOW_GENERATED_CONTENT | FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_IS_INLINE | FCDATA_USE_CHILD_ITEMS);
+ if (tag == nsGkAtoms::textPath) {
+ if (aAllowsTextPathChild) {
+ return &sTSpanData;
+ }
+ } else if (tag == nsGkAtoms::tspan || tag == nsGkAtoms::a) {
+ return &sTSpanData;
+ }
+ return &sSuppressData;
+ } else if (tag == nsGkAtoms::tspan || tag == nsGkAtoms::textPath) {
+ return &sSuppressData;
+ }
+
+ static constexpr FrameConstructionDataByTag sSVGData[] = {
+ SIMPLE_SVG_CREATE(svg, NS_NewSVGInnerSVGFrame),
+ SIMPLE_SVG_CREATE(g, NS_NewSVGGFrame),
+ SIMPLE_SVG_CREATE(svgSwitch, NS_NewSVGSwitchFrame),
+ SIMPLE_SVG_CREATE(symbol, NS_NewSVGSymbolFrame),
+ SIMPLE_SVG_CREATE(polygon, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(polyline, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(circle, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(ellipse, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(line, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(rect, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(path, NS_NewSVGGeometryFrame),
+ SIMPLE_SVG_CREATE(defs, NS_NewSVGContainerFrame),
+ {nsGkAtoms::text,
+ {NS_NewSVGTextFrame,
+ FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_ALLOW_BLOCK_STYLES,
+ PseudoStyleType::mozSVGText}},
+ {nsGkAtoms::foreignObject,
+ {ToCreationFunc(NS_NewSVGForeignObjectFrame),
+ FCDATA_DISALLOW_OUT_OF_FLOW, PseudoStyleType::mozSVGForeignContent}},
+ SIMPLE_SVG_CREATE(a, NS_NewSVGAFrame),
+ SIMPLE_SVG_CREATE(linearGradient, NS_NewSVGLinearGradientFrame),
+ SIMPLE_SVG_CREATE(radialGradient, NS_NewSVGRadialGradientFrame),
+ SIMPLE_SVG_CREATE(stop, NS_NewSVGStopFrame),
+ SIMPLE_SVG_CREATE(use, NS_NewSVGUseFrame),
+ SIMPLE_SVG_CREATE(view, NS_NewSVGViewFrame),
+ SIMPLE_SVG_CREATE(image, NS_NewSVGImageFrame),
+ SIMPLE_SVG_CREATE(clipPath, NS_NewSVGClipPathFrame),
+ SIMPLE_SVG_CREATE(filter, NS_NewSVGFilterFrame),
+ SIMPLE_SVG_CREATE(pattern, NS_NewSVGPatternFrame),
+ SIMPLE_SVG_CREATE(mask, NS_NewSVGMaskFrame),
+ SIMPLE_SVG_CREATE(feDistantLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(fePointLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feSpotLight, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feBlend, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feColorMatrix, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feFuncR, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncG, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncB, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feFuncA, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feComposite, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feComponentTransfer, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feConvolveMatrix, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feDiffuseLighting, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feDisplacementMap, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feDropShadow, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feFlood, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feGaussianBlur, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feImage, NS_NewSVGFEImageFrame),
+ SIMPLE_SVG_CREATE(feMerge, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feMergeNode, NS_NewSVGFEUnstyledLeafFrame),
+ SIMPLE_SVG_CREATE(feMorphology, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feOffset, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feSpecularLighting, NS_NewSVGFEContainerFrame),
+ SIMPLE_SVG_CREATE(feTile, NS_NewSVGFELeafFrame),
+ SIMPLE_SVG_CREATE(feTurbulence, NS_NewSVGFELeafFrame)};
+
+ const FrameConstructionData* data =
+ FindDataByTag(aElement, aStyle, sSVGData, ArrayLength(sSVGData));
+
+ if (!data) {
+ data = &sContainerData;
+ }
+
+ return data;
+}
+
+void nsCSSFrameConstructor::InsertPageBreakItem(
+ nsIContent* aContent, FrameConstructionItemList& aItems,
+ InsertPageBreakLocation location) {
+ RefPtr<ComputedStyle> pseudoStyle =
+ mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::pageBreak);
+
+ MOZ_ASSERT(pseudoStyle->StyleDisplay()->mDisplay == StyleDisplay::Block,
+ "Unexpected display");
+
+ static constexpr FrameConstructionData sPageBreakData(NS_NewPageBreakFrame,
+ FCDATA_SKIP_FRAMESET);
+ if (location == InsertPageBreakLocation::eBefore) {
+ aItems.PrependItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
+ true);
+ } else {
+ aItems.AppendItem(this, &sPageBreakData, aContent, pseudoStyle.forget(),
+ true);
+ }
+}
+
+bool nsCSSFrameConstructor::ShouldCreateItemsForChild(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame) {
+ aContent->UnsetFlags(NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME);
+ // XXX the GetContent() != aContent check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ if (aContent->GetPrimaryFrame() &&
+ aContent->GetPrimaryFrame()->GetContent() == aContent &&
+ !aState.mCreatingExtraFrames) {
+ MOZ_ASSERT(false,
+ "asked to create frame construction item for a node that "
+ "already has a frame");
+ return false;
+ }
+
+ // don't create a whitespace frame if aParent doesn't want it
+ if (!NeedFrameFor(aState, aParentFrame, aContent)) {
+ return false;
+ }
+
+ // never create frames for comments or PIs
+ if (aContent->IsComment() || aContent->IsProcessingInstruction()) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsCSSFrameConstructor::AddFrameConstructionItems(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ bool aSuppressWhiteSpaceOptimizations, const ComputedStyle& aParentStyle,
+ const InsertionPoint& aInsertion, FrameConstructionItemList& aItems,
+ ItemFlags aFlags) {
+ nsContainerFrame* parentFrame = aInsertion.mParentFrame;
+ if (!ShouldCreateItemsForChild(aState, aContent, parentFrame)) {
+ return;
+ }
+ if (MOZ_UNLIKELY(aParentStyle.StyleContent()->mContent.IsNone()) &&
+ StaticPrefs::layout_css_element_content_none_enabled()) {
+ return;
+ }
+
+ RefPtr<ComputedStyle> computedStyle = ResolveComputedStyle(aContent);
+ auto flags = aFlags + ItemFlag::AllowPageBreak;
+ if (parentFrame) {
+ if (parentFrame->IsInSVGTextSubtree()) {
+ flags += ItemFlag::IsWithinSVGText;
+ }
+ if (parentFrame->IsBlockFrame() && parentFrame->GetParent() &&
+ parentFrame->GetParent()->IsSVGTextFrame()) {
+ flags += ItemFlag::AllowTextPathChild;
+ }
+ }
+ AddFrameConstructionItemsInternal(aState, aContent, parentFrame,
+ aSuppressWhiteSpaceOptimizations,
+ computedStyle, flags, aItems);
+}
+
+// Whether we should suppress frames for a child under a <select> frame.
+//
+// Never create frames for non-option/optgroup kids of <select> and non-option
+// kids of <optgroup> inside a <select>.
+static bool ShouldSuppressFrameInSelect(const nsIContent* aParent,
+ const nsIContent& aChild) {
+ if (!aParent ||
+ !aParent->IsAnyOfHTMLElements(nsGkAtoms::select, nsGkAtoms::optgroup,
+ nsGkAtoms::option)) {
+ return false;
+ }
+
+ // Options with labels have their label text added in ::before by forms.css.
+ // Suppress frames for their child text.
+ if (aParent->IsHTMLElement(nsGkAtoms::option) &&
+ !aChild.IsRootOfNativeAnonymousSubtree()) {
+ return aParent->AsElement()->HasNonEmptyAttr(nsGkAtoms::label);
+ }
+
+ // If we're in any display: contents subtree, just suppress the frame.
+ //
+ // We can't be regular NAC, since display: contents has no frame to generate
+ // them off.
+ if (aChild.GetParent() != aParent) {
+ return true;
+ }
+
+ // Option is always fine.
+ if (aChild.IsHTMLElement(nsGkAtoms::option)) {
+ return false;
+ }
+
+ // <optgroup> is OK in <select> but not in <optgroup>.
+ if (aChild.IsHTMLElement(nsGkAtoms::optgroup) &&
+ aParent->IsHTMLElement(nsGkAtoms::select)) {
+ return false;
+ }
+
+ // Allow native anonymous content no matter what.
+ if (aChild.IsRootOfNativeAnonymousSubtree()) {
+ return false;
+ }
+
+ return true;
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindDataForContent(nsIContent& aContent,
+ ComputedStyle& aStyle,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags) {
+ MOZ_ASSERT(aStyle.StyleDisplay()->mDisplay != StyleDisplay::None &&
+ aStyle.StyleDisplay()->mDisplay != StyleDisplay::Contents,
+ "These two special display values should be handled earlier");
+
+ if (auto* text = Text::FromNode(aContent)) {
+ return FindTextData(*text, aParentFrame);
+ }
+
+ return FindElementData(*aContent.AsElement(), aStyle, aParentFrame, aFlags);
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindElementData(const Element& aElement,
+ ComputedStyle& aStyle,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags) {
+ // Don't create frames for non-SVG element children of SVG elements.
+ if (!aElement.IsSVGElement()) {
+ if (aParentFrame && IsFrameForSVG(aParentFrame) &&
+ !aParentFrame->IsSVGForeignObjectFrame()) {
+ return nullptr;
+ }
+ if (aFlags.contains(ItemFlag::IsWithinSVGText)) {
+ return nullptr;
+ }
+ }
+
+ if (auto* data = FindElementTagData(aElement, aStyle, aParentFrame, aFlags)) {
+ return data;
+ }
+
+ // Check for 'content: <image-url>' on the element (which makes us ignore
+ // 'display' values other than 'none' or 'contents').
+ if (nsImageFrame::ShouldCreateImageFrameForContentProperty(aElement,
+ aStyle)) {
+ static constexpr FrameConstructionData sImgData(
+ NS_NewImageFrameForContentProperty);
+ return &sImgData;
+ }
+
+ const bool shouldBlockify = aFlags.contains(ItemFlag::IsForRenderedLegend) ||
+ aFlags.contains(ItemFlag::IsForOutsideMarker);
+ if (shouldBlockify && !aStyle.StyleDisplay()->IsBlockOutsideStyle()) {
+ // Make a temp copy of StyleDisplay and blockify its mDisplay value.
+ auto display = *aStyle.StyleDisplay();
+ bool isRootElement = false;
+ uint16_t rawDisplayValue =
+ Servo_ComputedValues_BlockifiedDisplay(&aStyle, isRootElement);
+ display.mDisplay = StyleDisplay{rawDisplayValue};
+ return FindDisplayData(display, aElement);
+ }
+
+ const auto& display = *aStyle.StyleDisplay();
+ return FindDisplayData(display, aElement);
+}
+
+const nsCSSFrameConstructor::FrameConstructionData*
+nsCSSFrameConstructor::FindElementTagData(const Element& aElement,
+ ComputedStyle& aStyle,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags) {
+ switch (aElement.GetNameSpaceID()) {
+ case kNameSpaceID_XHTML:
+ return FindHTMLData(aElement, aParentFrame, aStyle);
+ case kNameSpaceID_MathML:
+ return FindMathMLData(aElement, aStyle);
+ case kNameSpaceID_SVG:
+ return FindSVGData(aElement, aParentFrame,
+ aFlags.contains(ItemFlag::IsWithinSVGText),
+ aFlags.contains(ItemFlag::AllowTextPathChild), aStyle);
+ case kNameSpaceID_XUL:
+ return FindXULTagData(aElement, aStyle);
+ default:
+ return nullptr;
+ }
+}
+
+void nsCSSFrameConstructor::AddFrameConstructionItemsInternal(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, bool aSuppressWhiteSpaceOptimizations,
+ ComputedStyle* aComputedStyle, ItemFlags aFlags,
+ FrameConstructionItemList& aItems) {
+ MOZ_ASSERT(aContent->IsText() || aContent->IsElement(),
+ "Shouldn't get anything else here!");
+ MOZ_ASSERT(aContent->IsInComposedDoc());
+ MOZ_ASSERT(!aContent->GetPrimaryFrame() || aState.mCreatingExtraFrames ||
+ aContent->NodeInfo()->NameAtom() == nsGkAtoms::area);
+
+ const bool withinSVGText = aFlags.contains(ItemFlag::IsWithinSVGText);
+ const bool isGeneratedContent = aFlags.contains(ItemFlag::IsGeneratedContent);
+ MOZ_ASSERT(!isGeneratedContent || aComputedStyle->IsPseudoElement(),
+ "Generated content should be a pseudo-element");
+
+ FrameConstructionItem* item = nullptr;
+ auto cleanupGeneratedContent = mozilla::MakeScopeExit([&]() {
+ if (isGeneratedContent && !item) {
+ MOZ_ASSERT(!IsDisplayContents(aContent),
+ "This would need to change if we support display: contents "
+ "in generated content");
+ aContent->UnbindFromTree();
+ }
+ });
+
+ // 'display:none' elements never creates any frames at all.
+ const nsStyleDisplay& display = *aComputedStyle->StyleDisplay();
+ if (display.mDisplay == StyleDisplay::None) {
+ return;
+ }
+
+ if (display.mDisplay == StyleDisplay::Contents) {
+ // See the mDisplay fixup code in StyleAdjuster::adjust.
+ MOZ_ASSERT(!aContent->AsElement()->IsRootOfNativeAnonymousSubtree(),
+ "display:contents on anonymous content is unsupported");
+
+ // FIXME(bug 1588477): <svg:text>'s TextNodeCorrespondenceRecorder has
+ // trouble with everything that looks like display: contents.
+ if (withinSVGText) {
+ return;
+ }
+
+ CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
+ *aComputedStyle, PseudoStyleType::before,
+ aItems);
+
+ FlattenedChildIterator iter(aContent);
+ InsertionPoint insertion(aParentFrame, aContent);
+ for (nsIContent* child = iter.GetNextChild(); child;
+ child = iter.GetNextChild()) {
+ AddFrameConstructionItems(aState, child, aSuppressWhiteSpaceOptimizations,
+ *aComputedStyle, insertion, aItems, aFlags);
+ }
+ aItems.SetParentHasNoShadowDOM(!iter.ShadowDOMInvolved());
+
+ CreateGeneratedContentItem(aState, aParentFrame, *aContent->AsElement(),
+ *aComputedStyle, PseudoStyleType::after, aItems);
+ return;
+ }
+
+ nsIContent* parent = aParentFrame ? aParentFrame->GetContent() : nullptr;
+ if (ShouldSuppressFrameInSelect(parent, *aContent)) {
+ return;
+ }
+
+ if (aContent->IsHTMLElement(nsGkAtoms::legend) && aParentFrame) {
+ const nsFieldSetFrame* const fs = GetFieldSetFrameFor(aParentFrame);
+ if (fs && !fs->GetLegend() && !aState.mHasRenderedLegend &&
+ !aComputedStyle->StyleDisplay()->IsFloatingStyle() &&
+ !aComputedStyle->StyleDisplay()->IsAbsolutelyPositionedStyle()) {
+ aState.mHasRenderedLegend = true;
+ aFlags += ItemFlag::IsForRenderedLegend;
+ }
+ }
+
+ const FrameConstructionData* const data =
+ FindDataForContent(*aContent, *aComputedStyle, aParentFrame, aFlags);
+ if (!data || data->mBits & FCDATA_SUPPRESS_FRAME) {
+ return;
+ }
+
+ const bool isPopup = data->mBits & FCDATA_IS_POPUP;
+
+ const uint32_t bits = data->mBits;
+
+ // Inside colgroups, suppress everything except columns.
+ if (aParentFrame && aParentFrame->IsTableColGroupFrame() &&
+ (!(bits & FCDATA_IS_TABLE_PART) ||
+ display.mDisplay != StyleDisplay::TableColumn)) {
+ return;
+ }
+
+ const bool canHavePageBreak =
+ aFlags.contains(ItemFlag::AllowPageBreak) &&
+ aState.mPresContext->IsPaginated() &&
+ !display.IsAbsolutelyPositionedStyle() &&
+ !(aParentFrame && aParentFrame->IsGridContainerFrame()) &&
+ !(bits & FCDATA_IS_TABLE_PART) && !(bits & FCDATA_IS_SVG_TEXT);
+ if (canHavePageBreak && display.BreakBefore()) {
+ AppendPageBreakItem(aContent, aItems);
+ }
+
+ if (!item) {
+ item = aItems.AppendItem(this, data, aContent, do_AddRef(aComputedStyle),
+ aSuppressWhiteSpaceOptimizations);
+ if (aFlags.contains(ItemFlag::IsForRenderedLegend)) {
+ item->mIsRenderedLegend = true;
+ }
+ }
+ item->mIsText = !aContent->IsElement();
+ item->mIsGeneratedContent = isGeneratedContent;
+ if (isGeneratedContent) {
+ // We need to keep this alive until the frame takes ownership.
+ // This corresponds to the Release in ConstructFramesFromItem.
+ item->mContent->AddRef();
+ }
+ item->mIsPopup = isPopup;
+
+ if (canHavePageBreak && display.BreakAfter()) {
+ AppendPageBreakItem(aContent, aItems);
+ }
+
+ if (bits & FCDATA_IS_INLINE) {
+ // To correctly set item->mIsAllInline we need to build up our child items
+ // right now.
+ BuildInlineChildItems(aState, *item,
+ aFlags.contains(ItemFlag::IsWithinSVGText),
+ aFlags.contains(ItemFlag::AllowTextPathChild));
+ item->mIsBlock = false;
+ } else {
+ // Compute a boolean isInline which is guaranteed to be false for blocks
+ // (but may also be false for some inlines).
+ const bool isInline =
+ // Table-internal things are inline-outside if and only if they're kids
+ // of inlines, since they'll trigger construction of inline-table
+ // pseudos.
+ ((bits & FCDATA_IS_TABLE_PART) &&
+ (!aParentFrame || // No aParentFrame means inline
+ aParentFrame->StyleDisplay()->IsInlineFlow())) ||
+ // Things that are inline-outside but aren't inline frames are inline
+ display.IsInlineOutsideStyle() ||
+ // Popups that are certainly out of flow.
+ isPopup;
+
+ // Set mIsAllInline conservatively. It just might be that even an inline
+ // that has mIsAllInline false doesn't need an {ib} split. So this is just
+ // an optimization to keep from doing too much work in cases when we can
+ // show that mIsAllInline is true..
+ item->mIsAllInline =
+ isInline ||
+ // Figure out whether we're guaranteed this item will be out of flow.
+ // This is not a precise test, since one of our ancestor inlines might
+ // add an absolute containing block (if it's relatively positioned) when
+ // there wasn't such a containing block before. But it's conservative
+ // in the sense that anything that will really end up as an in-flow
+ // non-inline will test false here. In other words, if this test is
+ // true we're guaranteed to be inline; if it's false we don't know what
+ // we'll end up as.
+ //
+ // If we make this test precise, we can remove some of the code dealing
+ // with the imprecision in ConstructInline and adjust the comments on
+ // mIsAllInline and mIsBlock in the header.
+ (!(bits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
+ aState.GetGeometricParent(display, nullptr));
+
+ // Set mIsBlock conservatively. It's OK to set it false for some real
+ // blocks, but not OK to set it true for things that aren't blocks. Since
+ // isOutOfFlow might be false even in cases when the frame will end up
+ // out-of-flow, we can't use it here. But we _can_ say that the frame will
+ // for sure end up in-flow if it's not floated or absolutely positioned.
+ item->mIsBlock = !isInline && !display.IsAbsolutelyPositionedStyle() &&
+ !display.IsFloatingStyle() && !(bits & FCDATA_IS_SVG_TEXT);
+ }
+
+ if (item->mIsAllInline) {
+ aItems.InlineItemAdded();
+ } else if (item->mIsBlock) {
+ aItems.BlockItemAdded();
+ }
+}
+
+/**
+ * Return true if the frame construction item pointed to by aIter will
+ * create a frame adjacent to a line boundary in the frame tree, and that
+ * line boundary is induced by a content node adjacent to the frame's
+ * content node in the content tree. The latter condition is necessary so
+ * that ContentAppended/ContentInserted/ContentRemoved can easily find any
+ * text nodes that were suppressed here.
+ */
+bool nsCSSFrameConstructor::AtLineBoundary(FCItemIterator& aIter) {
+ if (aIter.item().mSuppressWhiteSpaceOptimizations) {
+ return false;
+ }
+
+ if (aIter.AtStart()) {
+ if (aIter.List()->HasLineBoundaryAtStart() &&
+ !aIter.item().mContent->GetPreviousSibling())
+ return true;
+ } else {
+ FCItemIterator prev = aIter;
+ prev.Prev();
+ if (prev.item().IsLineBoundary() &&
+ !prev.item().mSuppressWhiteSpaceOptimizations &&
+ aIter.item().mContent->GetPreviousSibling() == prev.item().mContent)
+ return true;
+ }
+
+ FCItemIterator next = aIter;
+ next.Next();
+ if (next.IsDone()) {
+ if (aIter.List()->HasLineBoundaryAtEnd() &&
+ !aIter.item().mContent->GetNextSibling())
+ return true;
+ } else {
+ if (next.item().IsLineBoundary() &&
+ !next.item().mSuppressWhiteSpaceOptimizations &&
+ aIter.item().mContent->GetNextSibling() == next.item().mContent)
+ return true;
+ }
+
+ return false;
+}
+
+void nsCSSFrameConstructor::ConstructFramesFromItem(
+ nsFrameConstructorState& aState, FCItemIterator& aIter,
+ nsContainerFrame* aParentFrame, nsFrameList& aFrameList) {
+ FrameConstructionItem& item = aIter.item();
+ ComputedStyle* computedStyle = item.mComputedStyle;
+ if (item.mIsText) {
+ // If this is collapsible whitespace next to a line boundary,
+ // don't create a frame. item.IsWhitespace() also sets the
+ // NS_CREATE_FRAME_IF_NON_WHITESPACE flag in the text node. (If we
+ // end up creating a frame, nsTextFrame::Init will clear the flag.)
+ // We don't do this for generated content, because some generated
+ // text content is empty text nodes that are about to be initialized.
+ // (We check mAdditionalStateBits because only the generated content
+ // container's frame construction item is marked with
+ // mIsGeneratedContent, and we might not have an aParentFrame.)
+ // We don't do it for content that may have Shadow DOM siblings / insertion
+ // points, because they make it difficult to correctly create the frame due
+ // to dynamic changes.
+ // We don't do it for SVG text, since we might need to position and
+ // measure the white space glyphs due to x/y/dx/dy attributes.
+ if (AtLineBoundary(aIter) &&
+ !computedStyle->StyleText()->WhiteSpaceOrNewlineIsSignificant() &&
+ aIter.List()->ParentHasNoShadowDOM() &&
+ !(aState.mAdditionalStateBits & NS_FRAME_GENERATED_CONTENT) &&
+ (item.mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) &&
+ !(item.mFCData->mBits & FCDATA_IS_SVG_TEXT) &&
+ !mAlwaysCreateFramesForIgnorableWhitespace && item.IsWhitespace(aState))
+ return;
+
+ ConstructTextFrame(item.mFCData, aState, item.mContent, aParentFrame,
+ computedStyle, aFrameList);
+ return;
+ }
+
+ AutoRestore<nsFrameState> savedStateBits(aState.mAdditionalStateBits);
+ if (item.mIsGeneratedContent) {
+ // Ensure that frames created here are all tagged with
+ // NS_FRAME_GENERATED_CONTENT.
+ aState.mAdditionalStateBits |= NS_FRAME_GENERATED_CONTENT;
+ }
+
+ // XXXbz maybe just inline ConstructFrameFromItemInternal here or something?
+ ConstructFrameFromItemInternal(item, aState, aParentFrame, aFrameList);
+
+ if (item.mIsGeneratedContent) {
+ // This corresponds to the AddRef in AddFrameConstructionItemsInternal.
+ // The frame owns the generated content now.
+ item.mContent->Release();
+
+ // Now that we've passed ownership of item.mContent to the frame, unset
+ // our generated content flag so we don't release or unbind it ourselves.
+ item.mIsGeneratedContent = false;
+ }
+}
+
+nsContainerFrame* nsCSSFrameConstructor::GetAbsoluteContainingBlock(
+ nsIFrame* aFrame, ContainingBlockType aType) {
+ // Starting with aFrame, look for a frame that is absolutely positioned or
+ // relatively positioned (and transformed, if aType is FIXED)
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ if (frame->IsMathMLFrame()) {
+ // If it's mathml, bail out -- no absolute positioning out from inside
+ // mathml frames. Note that we don't make this part of the loop
+ // condition because of the stuff at the end of this method...
+ return nullptr;
+ }
+
+ // Look for the ICB.
+ if (aType == FIXED_POS) {
+ LayoutFrameType t = frame->Type();
+ if (t == LayoutFrameType::Viewport || t == LayoutFrameType::PageContent) {
+ return static_cast<nsContainerFrame*>(frame);
+ }
+ }
+
+ // If the frame is positioned, we will probably return it as the containing
+ // block (see the exceptions below). Otherwise, we'll start looking at the
+ // parent frame, unless we're dealing with a scrollframe.
+ // Scrollframes are special since they're not positioned, but their
+ // scrolledframe might be. So, we need to check this special case to return
+ // the correct containing block (the scrolledframe) in that case.
+ // If we're looking for a fixed-pos containing block and the frame is
+ // not transformed, skip it.
+ if (!frame->IsAbsPosContainingBlock()) {
+ continue;
+ }
+ if (aType == FIXED_POS && !frame->IsFixedPosContainingBlock()) {
+ continue;
+ }
+ nsIFrame* absPosCBCandidate = frame;
+ LayoutFrameType type = absPosCBCandidate->Type();
+ if (type == LayoutFrameType::FieldSet) {
+ absPosCBCandidate =
+ static_cast<nsFieldSetFrame*>(absPosCBCandidate)->GetInner();
+ if (!absPosCBCandidate) {
+ continue;
+ }
+ type = absPosCBCandidate->Type();
+ }
+ if (type == LayoutFrameType::Scroll) {
+ nsIScrollableFrame* scrollFrame = do_QueryFrame(absPosCBCandidate);
+ absPosCBCandidate = scrollFrame->GetScrolledFrame();
+ if (!absPosCBCandidate) {
+ continue;
+ }
+ type = absPosCBCandidate->Type();
+ }
+ // Only first continuations can be containing blocks.
+ absPosCBCandidate = absPosCBCandidate->FirstContinuation();
+ // Is the frame really an absolute container?
+ if (!absPosCBCandidate->IsAbsoluteContainer()) {
+ continue;
+ }
+
+ // For tables, skip the inner frame and consider the table wrapper frame.
+ if (type == LayoutFrameType::Table) {
+ continue;
+ }
+ // For table wrapper frames, we can just return absPosCBCandidate.
+ MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(absPosCBCandidate),
+ "abs.pos. containing block must be nsContainerFrame sub-class");
+ return static_cast<nsContainerFrame*>(absPosCBCandidate);
+ }
+
+ MOZ_ASSERT(aType != FIXED_POS, "no ICB in this frame tree?");
+
+ // It is possible for the search for the containing block to fail, because
+ // no absolute container can be found in the parent chain. In those cases,
+ // we fall back to the document element's containing block.
+ return mDocElementContainingBlock;
+}
+
+nsContainerFrame* nsCSSFrameConstructor::GetFloatContainingBlock(
+ nsIFrame* aFrame) {
+ // Starting with aFrame, look for a frame that is a float containing block.
+ // If we hit a frame which prevents its descendants from floating, bail out.
+ // The logic here needs to match the logic in MaybePushFloatContainingBlock().
+ for (nsIFrame* containingBlock = aFrame;
+ containingBlock && !ShouldSuppressFloatingOfDescendants(containingBlock);
+ containingBlock = containingBlock->GetParent()) {
+ if (containingBlock->IsFloatContainingBlock()) {
+ MOZ_ASSERT((nsContainerFrame*)do_QueryFrame(containingBlock),
+ "float containing block must be nsContainerFrame sub-class");
+ return static_cast<nsContainerFrame*>(containingBlock);
+ }
+ }
+
+ // If we didn't find a containing block, then there just isn't
+ // one.... return null
+ return nullptr;
+}
+
+/**
+ * This function will get the previous sibling to use for an append operation.
+ *
+ * It takes a parent frame (must not be null) and the next insertion sibling, if
+ * the parent content is display: contents or has ::after content (may be null).
+ */
+static nsIFrame* FindAppendPrevSibling(nsIFrame* aParentFrame,
+ nsIFrame* aNextSibling) {
+ aParentFrame->DrainSelfOverflowList();
+
+ if (aNextSibling) {
+ MOZ_ASSERT(
+ aNextSibling->GetParent()->GetContentInsertionFrame() == aParentFrame,
+ "Wrong parent");
+ return aNextSibling->GetPrevSibling();
+ }
+
+ return aParentFrame->PrincipalChildList().LastChild();
+}
+
+/**
+ * Finds the right parent frame to append content to aParentFrame.
+ *
+ * Cannot return or receive null.
+ */
+static nsContainerFrame* ContinuationToAppendTo(
+ nsContainerFrame* aParentFrame) {
+ MOZ_ASSERT(aParentFrame);
+
+ if (IsFramePartOfIBSplit(aParentFrame)) {
+ // If the frame we are manipulating is a ib-split frame (that is, one that's
+ // been created as a result of a block-in-inline situation) then we need to
+ // append to the last ib-split sibling, not to the frame itself.
+ //
+ // Always make sure to look at the last continuation of the frame for the
+ // {ib} case, even if that continuation is empty.
+ //
+ // We don't do this for the non-ib-split-frame case, since in the other
+ // cases appending to the last nonempty continuation is fine and in fact not
+ // doing that can confuse code that doesn't know to pull kids from
+ // continuations other than its next one.
+ return static_cast<nsContainerFrame*>(
+ GetLastIBSplitSibling(aParentFrame)->LastContinuation());
+ }
+
+ return nsLayoutUtils::LastContinuationWithChild(aParentFrame);
+}
+
+/**
+ * This function will get the next sibling for a frame insert operation given
+ * the parent and previous sibling. aPrevSibling may be null.
+ */
+static nsIFrame* GetInsertNextSibling(nsIFrame* aParentFrame,
+ nsIFrame* aPrevSibling) {
+ if (aPrevSibling) {
+ return aPrevSibling->GetNextSibling();
+ }
+
+ return aParentFrame->PrincipalChildList().FirstChild();
+}
+
+void nsCSSFrameConstructor::AppendFramesToParent(
+ nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList, nsIFrame* aPrevSibling, bool aIsRecursiveCall) {
+ MOZ_ASSERT(
+ !IsFramePartOfIBSplit(aParentFrame) || !GetIBSplitSibling(aParentFrame) ||
+ !GetIBSplitSibling(aParentFrame)->PrincipalChildList().FirstChild(),
+ "aParentFrame has a ib-split sibling with kids?");
+ MOZ_ASSERT(!aPrevSibling || aPrevSibling->GetParent() == aParentFrame,
+ "Parent and prevsibling don't match");
+ MOZ_ASSERT(
+ !aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) ||
+ !IsFramePartOfIBSplit(aParentFrame),
+ "We should have wiped aParentFrame in WipeContainingBlock() "
+ "if it's part of an IB split!");
+
+ nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling);
+
+ NS_ASSERTION(nextSibling || !aParentFrame->GetNextContinuation() ||
+ !aParentFrame->GetNextContinuation()
+ ->PrincipalChildList()
+ .FirstChild() ||
+ aIsRecursiveCall,
+ "aParentFrame has later continuations with kids?");
+ NS_ASSERTION(
+ nextSibling || !IsFramePartOfIBSplit(aParentFrame) ||
+ (IsInlineFrame(aParentFrame) && !GetIBSplitSibling(aParentFrame) &&
+ !aParentFrame->GetNextContinuation()) ||
+ aIsRecursiveCall,
+ "aParentFrame is not last?");
+
+ // If we're inserting a list of frames at the end of the trailing inline
+ // of an {ib} split, we may need to create additional {ib} siblings to parent
+ // them.
+ if (!nextSibling && IsFramePartOfIBSplit(aParentFrame)) {
+ // When we get here, our frame list might start with a block. If it does
+ // so, and aParentFrame is an inline, and it and all its previous
+ // continuations have no siblings, then put the initial blocks from the
+ // frame list into the previous block of the {ib} split. Note that we
+ // didn't want to stop at the block part of the split when figuring out
+ // initial parent, because that could screw up float parenting; it's easier
+ // to do this little fixup here instead.
+ if (aFrameList.NotEmpty() && aFrameList.FirstChild()->IsBlockOutside()) {
+ // See whether our trailing inline is empty
+ nsIFrame* firstContinuation = aParentFrame->FirstContinuation();
+ if (firstContinuation->PrincipalChildList().IsEmpty()) {
+ // Our trailing inline is empty. Collect our starting blocks from
+ // aFrameList, get the right parent frame for them, and put them in.
+ nsFrameList blockKids =
+ aFrameList.Split([](nsIFrame* f) { return !f->IsBlockOutside(); });
+ NS_ASSERTION(blockKids.NotEmpty(), "No blocks?");
+
+ nsContainerFrame* prevBlock = GetIBSplitPrevSibling(firstContinuation);
+ prevBlock =
+ static_cast<nsContainerFrame*>(prevBlock->LastContinuation());
+ NS_ASSERTION(prevBlock, "Should have previous block here");
+
+ MoveChildrenTo(aParentFrame, prevBlock, blockKids);
+ }
+ }
+
+ // We want to put some of the frames into this inline frame.
+ nsFrameList inlineKids =
+ aFrameList.Split([](nsIFrame* f) { return f->IsBlockOutside(); });
+
+ if (!inlineKids.IsEmpty()) {
+ AppendFrames(aParentFrame, FrameChildListID::Principal,
+ std::move(inlineKids));
+ }
+
+ if (!aFrameList.IsEmpty()) {
+ nsFrameList ibSiblings;
+ CreateIBSiblings(aState, aParentFrame,
+ aParentFrame->IsAbsPosContainingBlock(), aFrameList,
+ ibSiblings);
+
+ // Make sure to trigger reflow of the inline that used to be our
+ // last one and now isn't anymore, since its GetSkipSides() has
+ // changed.
+ mPresShell->FrameNeedsReflow(aParentFrame,
+ IntrinsicDirty::FrameAndAncestors,
+ NS_FRAME_HAS_DIRTY_CHILDREN);
+
+ // Recurse so we create new ib siblings as needed for aParentFrame's
+ // parent
+ return AppendFramesToParent(aState, aParentFrame->GetParent(), ibSiblings,
+ aParentFrame, true);
+ }
+ return;
+ }
+
+ // If we're appending a list of frames to the last continuations of a
+ // ::-moz-column-content, we may need to create column-span siblings for them.
+ if (!nextSibling && IsLastContinuationForColumnContent(aParentFrame)) {
+ // Extract any initial non-column-span kids, and append them to
+ // ::-moz-column-content's child list.
+ nsFrameList initialNonColumnSpanKids =
+ aFrameList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+ AppendFrames(aParentFrame, FrameChildListID::Principal,
+ std::move(initialNonColumnSpanKids));
+
+ if (aFrameList.IsEmpty()) {
+ // No more kids to process (there weren't any column-span kids).
+ return;
+ }
+
+ nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
+ aState, aParentFrame, aFrameList,
+ // Column content should never be a absolute/fixed positioned containing
+ // block. Pass nullptr as aPositionedFrame.
+ nullptr);
+
+ nsContainerFrame* columnSetWrapper = aParentFrame->GetParent();
+ while (!columnSetWrapper->IsColumnSetWrapperFrame()) {
+ columnSetWrapper = columnSetWrapper->GetParent();
+ }
+ MOZ_ASSERT(columnSetWrapper,
+ "No ColumnSetWrapperFrame ancestor for -moz-column-content?");
+
+ FinishBuildingColumns(aState, columnSetWrapper, aParentFrame,
+ columnSpanSiblings);
+
+ MOZ_ASSERT(columnSpanSiblings.IsEmpty(),
+ "The column-span siblings should be moved to the proper place!");
+ return;
+ }
+
+ // Insert the frames after our aPrevSibling
+ InsertFrames(aParentFrame, FrameChildListID::Principal, aPrevSibling,
+ std::move(aFrameList));
+}
+
+// This gets called to see if the frames corresponding to aSibling and aContent
+// should be siblings in the frame tree. Although (1) rows and cols, (2) row
+// groups and col groups, (3) row groups and captions, (4) legends and content
+// inside fieldsets, (5) popups and other kids of the menu are siblings from a
+// content perspective, they are not considered siblings in the frame tree.
+bool nsCSSFrameConstructor::IsValidSibling(nsIFrame* aSibling,
+ nsIContent* aContent,
+ Maybe<StyleDisplay>& aDisplay) {
+ StyleDisplay siblingDisplay = aSibling->GetDisplay();
+ if (StyleDisplay::TableColumnGroup == siblingDisplay ||
+ StyleDisplay::TableColumn == siblingDisplay ||
+ StyleDisplay::TableCaption == siblingDisplay ||
+ StyleDisplay::TableHeaderGroup == siblingDisplay ||
+ StyleDisplay::TableRowGroup == siblingDisplay ||
+ StyleDisplay::TableFooterGroup == siblingDisplay) {
+ // if we haven't already, resolve a style to find the display type of
+ // aContent.
+ if (aDisplay.isNothing()) {
+ if (aContent->IsComment() || aContent->IsProcessingInstruction()) {
+ // Comments and processing instructions never have frames, so we should
+ // not try to generate styles for them.
+ return false;
+ }
+ // FIXME(emilio): This is buggy some times, see bug 1424656.
+ RefPtr<ComputedStyle> computedStyle = ResolveComputedStyle(aContent);
+ const nsStyleDisplay* display = computedStyle->StyleDisplay();
+ aDisplay.emplace(display->mDisplay);
+ }
+
+ StyleDisplay display = aDisplay.value();
+ // To have decent performance we want to return false in cases in which
+ // reordering the two siblings has no effect on display. To ensure
+ // correctness, we MUST return false in cases where the two siblings have
+ // the same desired parent type and live on different display lists.
+ // Specificaly, columns and column groups should only consider columns and
+ // column groups as valid siblings. Captions should only consider other
+ // captions. All other things should consider each other as valid
+ // siblings. The restriction in the |if| above on siblingDisplay is ok,
+ // because for correctness the only part that really needs to happen is to
+ // not consider captions, column groups, and row/header/footer groups
+ // siblings of each other. Treating a column or colgroup as a valid
+ // sibling of a non-table-related frame will just mean we end up reframing.
+ if ((siblingDisplay == StyleDisplay::TableCaption) !=
+ (display == StyleDisplay::TableCaption)) {
+ // One's a caption and the other is not. Not valid siblings.
+ return false;
+ }
+
+ if ((siblingDisplay == StyleDisplay::TableColumnGroup ||
+ siblingDisplay == StyleDisplay::TableColumn) !=
+ (display == StyleDisplay::TableColumnGroup ||
+ display == StyleDisplay::TableColumn)) {
+ // One's a column or column group and the other is not. Not valid
+ // siblings.
+ return false;
+ }
+ // Fall through; it's possible that the display type was overridden and
+ // a different sort of frame was constructed, so we may need to return false
+ // below.
+ }
+
+ return true;
+}
+
+// FIXME(emilio): If we ever kill IsValidSibling() we can simplify this quite a
+// bit (no need to pass aTargetContent or aTargetContentDisplay, and the
+// adjust() calls can be responsibility of the caller).
+template <nsCSSFrameConstructor::SiblingDirection aDirection>
+nsIFrame* nsCSSFrameConstructor::FindSiblingInternal(
+ FlattenedChildIterator& aIter, nsIContent* aTargetContent,
+ Maybe<StyleDisplay>& aTargetContentDisplay) {
+ auto adjust = [&](nsIFrame* aPotentialSiblingFrame) -> nsIFrame* {
+ return AdjustSiblingFrame(aPotentialSiblingFrame, aTargetContent,
+ aTargetContentDisplay, aDirection);
+ };
+
+ auto nextDomSibling = [](FlattenedChildIterator& aIter) -> nsIContent* {
+ return aDirection == SiblingDirection::Forward ? aIter.GetNextChild()
+ : aIter.GetPreviousChild();
+ };
+
+ auto getInsideMarkerFrame = [](const nsIContent* aContent) -> nsIFrame* {
+ auto* marker = nsLayoutUtils::GetMarkerFrame(aContent);
+ const bool isInsideMarker =
+ marker && marker->GetInFlowParent()->StyleList()->mListStylePosition ==
+ StyleListStylePosition::Inside;
+ return isInsideMarker ? marker : nullptr;
+ };
+
+ auto getNearPseudo = [&](const nsIContent* aContent) -> nsIFrame* {
+ if (aDirection == SiblingDirection::Forward) {
+ if (auto* marker = getInsideMarkerFrame(aContent)) {
+ return marker;
+ }
+ return nsLayoutUtils::GetBeforeFrame(aContent);
+ }
+ return nsLayoutUtils::GetAfterFrame(aContent);
+ };
+
+ auto getFarPseudo = [&](const nsIContent* aContent) -> nsIFrame* {
+ if (aDirection == SiblingDirection::Forward) {
+ return nsLayoutUtils::GetAfterFrame(aContent);
+ }
+ if (auto* before = nsLayoutUtils::GetBeforeFrame(aContent)) {
+ return before;
+ }
+ return getInsideMarkerFrame(aContent);
+ };
+
+ while (nsIContent* sibling = nextDomSibling(aIter)) {
+ // NOTE(emilio): It's important to check GetPrimaryFrame() before
+ // IsDisplayContents to get the correct insertion point when multiple
+ // siblings go from display: non-none to display: contents.
+ if (nsIFrame* primaryFrame = sibling->GetPrimaryFrame()) {
+ // XXX the GetContent() == sibling check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ if (primaryFrame->GetContent() == sibling) {
+ if (nsIFrame* frame = adjust(primaryFrame)) {
+ return frame;
+ }
+ }
+ }
+
+ if (IsDisplayContents(sibling)) {
+ if (nsIFrame* frame = adjust(getNearPseudo(sibling))) {
+ return frame;
+ }
+
+ const bool startFromBeginning = aDirection == SiblingDirection::Forward;
+ FlattenedChildIterator iter(sibling, startFromBeginning);
+ nsIFrame* sibling = FindSiblingInternal<aDirection>(
+ iter, aTargetContent, aTargetContentDisplay);
+ if (sibling) {
+ return sibling;
+ }
+ }
+ }
+
+ return adjust(getFarPseudo(aIter.Parent()));
+}
+
+nsIFrame* nsCSSFrameConstructor::AdjustSiblingFrame(
+ nsIFrame* aSibling, nsIContent* aTargetContent,
+ Maybe<StyleDisplay>& aTargetContentDisplay, SiblingDirection aDirection) {
+ if (!aSibling) {
+ return nullptr;
+ }
+
+ if (aSibling->IsRenderedLegend()) {
+ return nullptr;
+ }
+
+ if (aSibling->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ aSibling = aSibling->GetPlaceholderFrame();
+ MOZ_ASSERT(aSibling);
+ }
+
+ MOZ_ASSERT(!aSibling->GetPrevContinuation(), "How?");
+ if (aDirection == SiblingDirection::Backward) {
+ // The frame may be a ib-split frame (a split inline frame that contains a
+ // block). Get the last part of that split.
+ if (IsFramePartOfIBSplit(aSibling)) {
+ aSibling = GetLastIBSplitSibling(aSibling);
+ }
+
+ // The frame may have a continuation. If so, we want the last
+ // non-overflow-container continuation as our previous sibling.
+ aSibling = aSibling->GetTailContinuation();
+ }
+
+ if (!IsValidSibling(aSibling, aTargetContent, aTargetContentDisplay)) {
+ return nullptr;
+ }
+
+ return aSibling;
+}
+
+nsIFrame* nsCSSFrameConstructor::FindPreviousSibling(
+ const FlattenedChildIterator& aIter,
+ Maybe<StyleDisplay>& aTargetContentDisplay) {
+ return FindSibling<SiblingDirection::Backward>(aIter, aTargetContentDisplay);
+}
+
+nsIFrame* nsCSSFrameConstructor::FindNextSibling(
+ const FlattenedChildIterator& aIter,
+ Maybe<StyleDisplay>& aTargetContentDisplay) {
+ return FindSibling<SiblingDirection::Forward>(aIter, aTargetContentDisplay);
+}
+
+template <nsCSSFrameConstructor::SiblingDirection aDirection>
+nsIFrame* nsCSSFrameConstructor::FindSibling(
+ const FlattenedChildIterator& aIter,
+ Maybe<StyleDisplay>& aTargetContentDisplay) {
+ nsIContent* targetContent = aIter.Get();
+ FlattenedChildIterator siblingIter = aIter;
+ nsIFrame* sibling = FindSiblingInternal<aDirection>(
+ siblingIter, targetContent, aTargetContentDisplay);
+ if (sibling) {
+ return sibling;
+ }
+
+ // Our siblings (if any) do not have a frame to guide us. The frame for the
+ // target content should be inserted whereever a frame for the container would
+ // be inserted. This is needed when inserting into display: contents nodes.
+ const nsIContent* current = aIter.Parent();
+ while (IsDisplayContents(current)) {
+ const nsIContent* parent = current->GetFlattenedTreeParent();
+ MOZ_ASSERT(parent, "No display: contents on the root");
+
+ FlattenedChildIterator iter(parent);
+ iter.Seek(current);
+ sibling = FindSiblingInternal<aDirection>(iter, targetContent,
+ aTargetContentDisplay);
+ if (sibling) {
+ return sibling;
+ }
+
+ current = parent;
+ }
+
+ return nullptr;
+}
+
+// For fieldsets, returns the area frame, if the child is not a legend.
+static nsContainerFrame* GetAdjustedParentFrame(nsContainerFrame* aParentFrame,
+ nsIContent* aChildContent) {
+ MOZ_ASSERT(!aParentFrame->IsTableWrapperFrame(), "Shouldn't be happening!");
+
+ nsContainerFrame* newParent = nullptr;
+ if (aParentFrame->IsFieldSetFrame()) {
+ // If the parent is a fieldSet, use the fieldSet's area frame as the
+ // parent unless the new content is a legend.
+ if (!aChildContent->IsHTMLElement(nsGkAtoms::legend)) {
+ newParent = static_cast<nsFieldSetFrame*>(aParentFrame)->GetInner();
+ if (newParent) {
+ newParent = newParent->GetContentInsertionFrame();
+ }
+ }
+ }
+ return newParent ? newParent : aParentFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::GetInsertionPrevSibling(
+ InsertionPoint* aInsertion, nsIContent* aChild, bool* aIsAppend,
+ bool* aIsRangeInsertSafe, nsIContent* aStartSkipChild,
+ nsIContent* aEndSkipChild) {
+ MOZ_ASSERT(aInsertion->mParentFrame, "Must have parent frame to start with");
+
+ *aIsAppend = false;
+
+ // Find the frame that precedes the insertion point.
+ FlattenedChildIterator iter(aInsertion->mContainer);
+ if (iter.ShadowDOMInvolved() || !aChild->IsRootOfNativeAnonymousSubtree()) {
+ // The check for IsRootOfNativeAnonymousSubtree() is because editor is
+ // severely broken and calls us directly for native anonymous
+ // nodes that it creates.
+ if (aStartSkipChild) {
+ iter.Seek(aStartSkipChild);
+ } else {
+ iter.Seek(aChild);
+ }
+ } else {
+ // Prime the iterator for the call to FindPreviousSibling.
+ iter.GetNextChild();
+ MOZ_ASSERT(aChild->GetProperty(nsGkAtoms::restylableAnonymousNode),
+ "Someone passed native anonymous content directly into frame "
+ "construction. Stop doing that!");
+ }
+
+ // Note that FindPreviousSibling is passed the iterator by value, so that
+ // the later usage of the iterator starts from the same place.
+ Maybe<StyleDisplay> childDisplay;
+ nsIFrame* prevSibling = FindPreviousSibling(iter, childDisplay);
+
+ // Now, find the geometric parent so that we can handle
+ // continuations properly. Use the prev sibling if we have it;
+ // otherwise use the next sibling.
+ if (prevSibling) {
+ aInsertion->mParentFrame =
+ prevSibling->GetParent()->GetContentInsertionFrame();
+ } else {
+ // If there is no previous sibling, then find the frame that follows
+ //
+ // FIXME(emilio): This is really complex and probably shouldn't be.
+ if (aEndSkipChild) {
+ iter.Seek(aEndSkipChild);
+ iter.GetPreviousChild();
+ }
+ if (nsIFrame* nextSibling = FindNextSibling(iter, childDisplay)) {
+ aInsertion->mParentFrame =
+ nextSibling->GetParent()->GetContentInsertionFrame();
+ } else {
+ // No previous or next sibling, so treat this like an appended frame.
+ *aIsAppend = true;
+
+ // Deal with fieldsets.
+ aInsertion->mParentFrame =
+ ::GetAdjustedParentFrame(aInsertion->mParentFrame, aChild);
+
+ aInsertion->mParentFrame =
+ ::ContinuationToAppendTo(aInsertion->mParentFrame);
+
+ prevSibling = ::FindAppendPrevSibling(aInsertion->mParentFrame, nullptr);
+ }
+ }
+
+ *aIsRangeInsertSafe = childDisplay.isNothing();
+ return prevSibling;
+}
+
+nsContainerFrame* nsCSSFrameConstructor::GetContentInsertionFrameFor(
+ nsIContent* aContent) {
+ nsIFrame* frame;
+ while (!(frame = aContent->GetPrimaryFrame())) {
+ if (!IsDisplayContents(aContent)) {
+ return nullptr;
+ }
+
+ aContent = aContent->GetFlattenedTreeParent();
+ if (!aContent) {
+ return nullptr;
+ }
+ }
+
+ // If the content of the frame is not the desired content then this is not
+ // really a frame for the desired content.
+ // XXX This check is needed due to bug 135040. Remove it once that's fixed.
+ if (frame->GetContent() != aContent) {
+ return nullptr;
+ }
+
+ nsContainerFrame* insertionFrame = frame->GetContentInsertionFrame();
+
+ NS_ASSERTION(!insertionFrame || insertionFrame == frame || !frame->IsLeaf(),
+ "The insertion frame is the primary frame or the primary frame "
+ "isn't a leaf");
+
+ return insertionFrame;
+}
+
+static bool IsSpecialFramesetChild(nsIContent* aContent) {
+ // IMPORTANT: This must match the conditions in nsHTMLFramesetFrame::Init.
+ return aContent->IsAnyOfHTMLElements(nsGkAtoms::frameset, nsGkAtoms::frame);
+}
+
+static void InvalidateCanvasIfNeeded(PresShell* aPresShell, nsIContent* aNode);
+
+void nsCSSFrameConstructor::AddTextItemIfNeeded(
+ nsFrameConstructorState& aState, const ComputedStyle& aParentStyle,
+ const InsertionPoint& aInsertion, nsIContent* aPossibleTextContent,
+ FrameConstructionItemList& aItems) {
+ MOZ_ASSERT(aPossibleTextContent, "Must have node");
+ if (!aPossibleTextContent->IsText() ||
+ !aPossibleTextContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
+ aPossibleTextContent->HasFlag(NODE_NEEDS_FRAME)) {
+ // Not text, or not suppressed due to being all-whitespace (if it were being
+ // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or
+ // going to be reframed anyway.
+ return;
+ }
+ MOZ_ASSERT(!aPossibleTextContent->GetPrimaryFrame(),
+ "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+ AddFrameConstructionItems(aState, aPossibleTextContent, false, aParentStyle,
+ aInsertion, aItems);
+}
+
+void nsCSSFrameConstructor::ReframeTextIfNeeded(nsIContent* aContent) {
+ if (!aContent->IsText() ||
+ !aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) ||
+ aContent->HasFlag(NODE_NEEDS_FRAME)) {
+ // Not text, or not suppressed due to being all-whitespace (if it were being
+ // suppressed, it would have the NS_CREATE_FRAME_IF_NON_WHITESPACE flag), or
+ // going to be reframed anyway.
+ return;
+ }
+ MOZ_ASSERT(!aContent->GetPrimaryFrame(),
+ "Text node has a frame and NS_CREATE_FRAME_IF_NON_WHITESPACE");
+ ContentInserted(aContent, InsertionKind::Async);
+}
+
+#ifdef DEBUG
+void nsCSSFrameConstructor::CheckBitsForLazyFrameConstruction(
+ nsIContent* aParent) {
+ // If we hit a node with no primary frame, or the NODE_NEEDS_FRAME bit set
+ // we want to assert, but leaf frames that process their own children and may
+ // ignore anonymous children (eg framesets) make this complicated. So we set
+ // these two booleans if we encounter these situations and unset them if we
+ // hit a node with a leaf frame.
+ //
+ // It's fine if one of node without primary frame is in a display:none
+ // subtree.
+ //
+ // Also, it's fine if one of the nodes without primary frame is a display:
+ // contents node.
+ bool noPrimaryFrame = false;
+ bool needsFrameBitSet = false;
+ nsIContent* content = aParent;
+ while (content && !content->HasFlag(NODE_DESCENDANTS_NEED_FRAMES)) {
+ if (content->GetPrimaryFrame() && content->GetPrimaryFrame()->IsLeaf()) {
+ noPrimaryFrame = needsFrameBitSet = false;
+ }
+ if (!noPrimaryFrame && !content->GetPrimaryFrame()) {
+ noPrimaryFrame = !IsDisplayContents(content);
+ }
+ if (!needsFrameBitSet && content->HasFlag(NODE_NEEDS_FRAME)) {
+ needsFrameBitSet = true;
+ }
+
+ content = content->GetFlattenedTreeParent();
+ }
+ if (content && content->GetPrimaryFrame() &&
+ content->GetPrimaryFrame()->IsLeaf()) {
+ noPrimaryFrame = needsFrameBitSet = false;
+ }
+ MOZ_ASSERT(!noPrimaryFrame,
+ "Ancestors of nodes with frames to be "
+ "constructed lazily should have frames");
+ MOZ_ASSERT(!needsFrameBitSet,
+ "Ancestors of nodes with frames to be "
+ "constructed lazily should not have NEEDS_FRAME bit set");
+}
+#endif
+
+// Returns true if this operation can be lazy, false if not.
+//
+// FIXME(emilio, bug 1410020): This function assumes that the flattened tree
+// parent of all the appended children is the same, which, afaict, is not
+// necessarily true.
+void nsCSSFrameConstructor::ConstructLazily(Operation aOperation,
+ nsIContent* aChild) {
+ MOZ_ASSERT(aChild->GetParent());
+
+ // We can construct lazily; just need to set suitable bits in the content
+ // tree.
+ Element* parent = aChild->GetFlattenedTreeParentElement();
+ if (!parent) {
+ // Not part of the flat tree, nothing to do.
+ return;
+ }
+
+ if (Servo_Element_IsDisplayNone(parent)) {
+ // Nothing to do either.
+ //
+ // FIXME(emilio): This should be an assert, except for weird <frameset>
+ // stuff that does its own frame construction. Such an assert would fire in
+ // layout/style/crashtests/1411478.html, for example.
+ return;
+ }
+
+ // Set NODE_NEEDS_FRAME on the new nodes.
+ if (aOperation == CONTENTINSERT) {
+ NS_ASSERTION(!aChild->GetPrimaryFrame() ||
+ aChild->GetPrimaryFrame()->GetContent() != aChild,
+ // XXX the aChild->GetPrimaryFrame()->GetContent() != aChild
+ // check is needed due to bug 135040. Remove it once that's
+ // fixed.
+ "setting NEEDS_FRAME on a node that already has a frame?");
+ aChild->SetFlags(NODE_NEEDS_FRAME);
+ } else { // CONTENTAPPEND
+ for (nsIContent* child = aChild; child; child = child->GetNextSibling()) {
+ NS_ASSERTION(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ // XXX the child->GetPrimaryFrame()->GetContent() != child
+ // check is needed due to bug 135040. Remove it once that's
+ // fixed.
+ "setting NEEDS_FRAME on a node that already has a frame?");
+ child->SetFlags(NODE_NEEDS_FRAME);
+ }
+ }
+
+ CheckBitsForLazyFrameConstruction(parent);
+ parent->NoteDescendantsNeedFramesForServo();
+}
+
+void nsCSSFrameConstructor::IssueSingleInsertNofications(
+ nsIContent* aStartChild, nsIContent* aEndChild,
+ InsertionKind aInsertionKind) {
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ // XXX the GetContent() != child check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ MOZ_ASSERT(!child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child);
+
+ // Call ContentRangeInserted with this node.
+ ContentRangeInserted(child, child->GetNextSibling(), aInsertionKind);
+ }
+}
+
+bool nsCSSFrameConstructor::InsertionPoint::IsMultiple() const {
+ // Fieldset frames have multiple normal flow child frame lists so handle it
+ // the same as if it had multiple content insertion points.
+ return mParentFrame && mParentFrame->IsFieldSetFrame();
+}
+
+nsCSSFrameConstructor::InsertionPoint
+nsCSSFrameConstructor::GetRangeInsertionPoint(nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ InsertionKind aInsertionKind) {
+ MOZ_ASSERT(aStartChild);
+
+ nsIContent* parent = aStartChild->GetParent();
+ if (!parent) {
+ IssueSingleInsertNofications(aStartChild, aEndChild, aInsertionKind);
+ return {};
+ }
+
+ // If the children of the container may be distributed to different insertion
+ // points, insert them separately and bail out, letting ContentInserted handle
+ // the mess.
+ if (parent->GetShadowRoot()) {
+ IssueSingleInsertNofications(aStartChild, aEndChild, aInsertionKind);
+ return {};
+ }
+
+#ifdef DEBUG
+ {
+ nsIContent* expectedParent = aStartChild->GetFlattenedTreeParent();
+ for (nsIContent* child = aStartChild->GetNextSibling(); child;
+ child = child->GetNextSibling()) {
+ MOZ_ASSERT(child->GetFlattenedTreeParent() == expectedParent);
+ }
+ }
+#endif
+
+ // Now the flattened tree parent of all the siblings is the same, just use the
+ // same insertion point and take the fast path, unless it's a multiple
+ // insertion point.
+ InsertionPoint ip = GetInsertionPoint(aStartChild);
+ if (ip.IsMultiple()) {
+ IssueSingleInsertNofications(aStartChild, aEndChild, aInsertionKind);
+ return {};
+ }
+
+ return ip;
+}
+
+bool nsCSSFrameConstructor::MaybeRecreateForFrameset(nsIFrame* aParentFrame,
+ nsIContent* aStartChild,
+ nsIContent* aEndChild) {
+ if (aParentFrame->IsFrameSetFrame()) {
+ // Check whether we have any kids we care about.
+ for (nsIContent* cur = aStartChild; cur != aEndChild;
+ cur = cur->GetNextSibling()) {
+ if (IsSpecialFramesetChild(cur)) {
+ // Just reframe the parent, since framesets are weird like that.
+ RecreateFramesForContent(aParentFrame->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void nsCSSFrameConstructor::LazilyStyleNewChildRange(nsIContent* aStartChild,
+ nsIContent* aEndChild) {
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ if (child->IsElement()) {
+ child->AsElement()->NoteDirtyForServo();
+ }
+ }
+}
+
+#ifdef DEBUG
+static bool IsFlattenedTreeChild(nsIContent* aParent, nsIContent* aChild) {
+ FlattenedChildIterator iter(aParent);
+ for (nsIContent* node = iter.GetNextChild(); node;
+ node = iter.GetNextChild()) {
+ if (node == aChild) {
+ return true;
+ }
+ }
+ return false;
+}
+#endif
+
+void nsCSSFrameConstructor::StyleNewChildRange(nsIContent* aStartChild,
+ nsIContent* aEndChild) {
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ if (!child->IsElement()) {
+ continue;
+ }
+
+ Element* childElement = child->AsElement();
+
+ // We only come in here from non-lazy frame construction, so the children
+ // should be unstyled.
+ MOZ_ASSERT(!childElement->HasServoData());
+
+#ifdef DEBUG
+ {
+ // Furthermore, all of them should have the same flattened tree parent
+ // (GetRangeInsertionPoint ensures it). And that parent should be styled,
+ // otherwise we would've never found an insertion point at all.
+ Element* parent = childElement->GetFlattenedTreeParentElement();
+ MOZ_ASSERT(parent);
+ MOZ_ASSERT(parent->HasServoData());
+ MOZ_ASSERT(
+ IsFlattenedTreeChild(parent, child),
+ "GetFlattenedTreeParent and ChildIterator don't agree, fix this!");
+ }
+#endif
+
+ styleSet->StyleNewSubtree(childElement);
+ }
+}
+
+nsIFrame* nsCSSFrameConstructor::FindNextSiblingForAppend(
+ const InsertionPoint& aInsertion) {
+ auto SlowPath = [&]() -> nsIFrame* {
+ FlattenedChildIterator iter(aInsertion.mContainer,
+ /* aStartAtBeginning = */ false);
+ iter.GetPreviousChild(); // Prime the iterator.
+ Maybe<StyleDisplay> unused;
+ return FindNextSibling(iter, unused);
+ };
+
+ if (!IsDisplayContents(aInsertion.mContainer) &&
+ !nsLayoutUtils::GetAfterFrame(aInsertion.mContainer)) {
+ MOZ_ASSERT(!SlowPath());
+ return nullptr;
+ }
+
+ return SlowPath();
+}
+
+void nsCSSFrameConstructor::ContentAppended(nsIContent* aFirstNewContent,
+ InsertionKind aInsertionKind) {
+ MOZ_ASSERT(aInsertionKind == InsertionKind::Sync ||
+ !RestyleManager()->IsInStyleRefresh());
+
+ AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ContentAppended",
+ LAYOUT_FrameConstruction);
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf(
+ "nsCSSFrameConstructor::ContentAppended container=%p "
+ "first-child=%p lazy=%d\n",
+ aFirstNewContent->GetParent(), aFirstNewContent,
+ aInsertionKind == InsertionKind::Async);
+ if (gReallyNoisyContentUpdates && aFirstNewContent->GetParent()) {
+ aFirstNewContent->GetParent()->List(stdout, 0);
+ }
+ }
+
+ for (nsIContent* child = aFirstNewContent; child;
+ child = child->GetNextSibling()) {
+ // XXX the GetContent() != child check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ MOZ_ASSERT(
+ !child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ "asked to construct a frame for a node that already has a frame");
+ }
+#endif
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ InsertionPoint insertion =
+ GetRangeInsertionPoint(aFirstNewContent, nullptr, aInsertionKind);
+ nsContainerFrame*& parentFrame = insertion.mParentFrame;
+ LAYOUT_PHASE_TEMP_REENTER();
+ if (!parentFrame) {
+ // We're punting on frame construction because there's no container frame.
+ // The Servo-backed style system handles this case like the lazy frame
+ // construction case, except when we're already constructing frames, in
+ // which case we shouldn't need to do anything else.
+ if (aInsertionKind == InsertionKind::Async) {
+ LazilyStyleNewChildRange(aFirstNewContent, nullptr);
+ }
+ return;
+ }
+
+ if (aInsertionKind == InsertionKind::Async) {
+ ConstructLazily(CONTENTAPPEND, aFirstNewContent);
+ LazilyStyleNewChildRange(aFirstNewContent, nullptr);
+ return;
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForFrameset(parentFrame, aFirstNewContent, nullptr)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ if (parentFrame->IsLeaf()) {
+ // Nothing to do here; we shouldn't be constructing kids of leaves
+ // Clear lazy bits so we don't try to construct again.
+ ClearLazyBits(aFirstNewContent, nullptr);
+ return;
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeInsertionParent(parentFrame)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates && IsFramePartOfIBSplit(parentFrame)) {
+ printf("nsCSSFrameConstructor::ContentAppended: parentFrame=");
+ parentFrame->ListTag(stdout);
+ printf(" is ib-split\n");
+ }
+#endif
+
+ // We should never get here with fieldsets, since they have
+ // multiple insertion points.
+ MOZ_ASSERT(!parentFrame->IsFieldSetFrame(),
+ "Parent frame should not be fieldset!");
+
+ nsIFrame* nextSibling = FindNextSiblingForAppend(insertion);
+ if (nextSibling) {
+ parentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
+ } else {
+ parentFrame = ::ContinuationToAppendTo(parentFrame);
+ }
+
+ nsContainerFrame* containingBlock = GetFloatContainingBlock(parentFrame);
+
+ // See if the containing block has :first-letter style applied.
+ const bool haveFirstLetterStyle =
+ containingBlock && HasFirstLetterStyle(containingBlock);
+
+ const bool haveFirstLineStyle =
+ containingBlock && ShouldHaveFirstLineStyle(containingBlock->GetContent(),
+ containingBlock->Style());
+
+ if (haveFirstLetterStyle) {
+ AutoWeakFrame wf(nextSibling);
+
+ // Before we get going, remove the current letter frames
+ RemoveLetterFrames(mPresShell, containingBlock);
+
+ // Reget nextSibling, since we may have killed it.
+ //
+ // FIXME(emilio): This kinda sucks! :(
+ if (nextSibling && !wf) {
+ nextSibling = FindNextSiblingForAppend(insertion);
+ if (nextSibling) {
+ parentFrame = nextSibling->GetParent()->GetContentInsertionFrame();
+ containingBlock = GetFloatContainingBlock(parentFrame);
+ }
+ }
+ }
+
+ // Create some new frames
+ nsFrameConstructorState state(
+ mPresShell, GetAbsoluteContainingBlock(parentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(parentFrame, ABS_POS), containingBlock);
+
+ if (mPresShell->GetPresContext()->IsPaginated()) {
+ // Because this function can be called outside frame construction, we need
+ // to set state.mAutoPageNameValue based on what the parent frame's auto
+ // value is.
+ // Calling this from outside the frame constructor can violate many of the
+ // expectations in AutoFrameConstructionPageName, and unlike during frame
+ // construction we already have an auto value from parentFrame, so we do
+ // not use AutoFrameConstructionPageName here.
+ state.mAutoPageNameValue = parentFrame->GetAutoPageValue();
+#ifdef DEBUG
+ parentFrame->mWasVisitedByAutoFrameConstructionPageName = true;
+#endif
+ }
+
+ LayoutFrameType frameType = parentFrame->Type();
+
+ RefPtr<ComputedStyle> parentStyle =
+ ResolveComputedStyle(insertion.mContainer);
+ FlattenedChildIterator iter(insertion.mContainer);
+ const bool haveNoShadowDOM =
+ !iter.ShadowDOMInvolved() || !iter.GetNextChild();
+
+ AutoFrameConstructionItemList items(this);
+ if (aFirstNewContent->GetPreviousSibling() &&
+ GetParentType(frameType) == eTypeBlock && haveNoShadowDOM) {
+ // If there's a text node in the normal content list just before the new
+ // items, and it has no frame, make a frame construction item for it. If it
+ // doesn't need a frame, ConstructFramesFromItemList below won't give it
+ // one. No need to do all this if our parent type is not block, though,
+ // since WipeContainingBlock already handles that situation.
+ //
+ // Because we're appending, we don't need to worry about any text
+ // after the appended content; there can only be generated content
+ // (and bare text nodes are not generated). Native anonymous content
+ // generated by frames never participates in inline layout.
+ AddTextItemIfNeeded(state, *parentStyle, insertion,
+ aFirstNewContent->GetPreviousSibling(), items);
+ }
+ for (nsIContent* child = aFirstNewContent; child;
+ child = child->GetNextSibling()) {
+ AddFrameConstructionItems(state, child, false, *parentStyle, insertion,
+ items);
+ }
+
+ nsIFrame* prevSibling = ::FindAppendPrevSibling(parentFrame, nextSibling);
+
+ // Perform special check for diddling around with the frames in
+ // a ib-split inline frame.
+ // If we're appending before :after content, then we're not really
+ // appending, so let WipeContainingBlock know that.
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeContainingBlock(state, containingBlock, parentFrame, items, true,
+ prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // If the parent is a block frame, and we're not in a special case
+ // where frames can be moved around, determine if the list is for the
+ // start or end of the block.
+ if (parentFrame->IsBlockFrameOrSubclass() && !haveFirstLetterStyle &&
+ !haveFirstLineStyle && !IsFramePartOfIBSplit(parentFrame)) {
+ items.SetLineBoundaryAtStart(!prevSibling ||
+ !prevSibling->IsInlineOutside() ||
+ prevSibling->IsBrFrame());
+ // :after content can't be <br> so no need to check it
+ //
+ // FIXME(emilio): A display: contents sibling could! Write a test-case and
+ // fix.
+ items.SetLineBoundaryAtEnd(!nextSibling || !nextSibling->IsInlineOutside());
+ }
+ // To suppress whitespace-only text frames, we have to verify that
+ // our container's DOM child list matches its flattened tree child list.
+ items.SetParentHasNoShadowDOM(haveNoShadowDOM);
+
+ nsFrameConstructorSaveState floatSaveState;
+ state.MaybePushFloatContainingBlock(parentFrame, floatSaveState);
+
+ nsFrameList frameList;
+ ConstructFramesFromItemList(state, items, parentFrame,
+ ParentIsWrapperAnonBox(parentFrame), frameList);
+
+ for (nsIContent* child = aFirstNewContent; child;
+ child = child->GetNextSibling()) {
+ // Invalidate now instead of before the WipeContainingBlock call, just in
+ // case we do wipe; in that case we don't need to do this walk at all.
+ // XXXbz does that matter? Would it make more sense to save some virtual
+ // GetChildAt_Deprecated calls instead and do this during construction of
+ // our FrameConstructionItemList?
+ InvalidateCanvasIfNeeded(mPresShell, child);
+ }
+
+ // If the container is a table and a caption was appended, it needs to be put
+ // in the table wrapper frame's additional child list.
+ nsFrameList captionList;
+ if (LayoutFrameType::Table == frameType) {
+ // Pull out the captions. Note that we don't want to do that as we go,
+ // because processing a single caption can add a whole bunch of things to
+ // the frame items due to pseudoframe processing. So we'd have to pull
+ // captions from a list anyway; might as well do that here.
+ // XXXbz this is no longer true; we could pull captions directly out of the
+ // FrameConstructionItemList now.
+ PullOutCaptionFrames(frameList, captionList);
+ }
+
+ if (haveFirstLineStyle && parentFrame == containingBlock) {
+ // It's possible that some of the new frames go into a
+ // first-line frame. Look at them and see...
+ AppendFirstLineFrames(state, containingBlock->GetContent(), containingBlock,
+ frameList);
+ // That moved things into line frames as needed, reparenting their
+ // styles. Nothing else needs to be done.
+ } else if (parentFrame->Style()->HasPseudoElementData()) {
+ // parentFrame might be inside a ::first-line frame. Check whether it is,
+ // and if so fix up our styles.
+ CheckForFirstLineInsertion(parentFrame, frameList);
+ CheckForFirstLineInsertion(parentFrame, captionList);
+ }
+
+ // Notify the parent frame passing it the list of new frames
+ // Append the flowed frames to the principal child list; captions
+ // need special treatment
+ if (captionList.NotEmpty()) { // append the caption to the table wrapper
+ NS_ASSERTION(LayoutFrameType::Table == frameType, "how did that happen?");
+ nsContainerFrame* outerTable = parentFrame->GetParent();
+ captionList.ApplySetParent(outerTable);
+ AppendFrames(outerTable, FrameChildListID::Caption, std::move(captionList));
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForColumnSpan(state, parentFrame, frameList, prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ if (frameList.NotEmpty()) { // append the in-flow kids
+ AppendFramesToParent(state, parentFrame, frameList, prevSibling);
+ }
+
+ // Recover first-letter frames
+ if (haveFirstLetterStyle) {
+ RecoverLetterFrames(containingBlock);
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentAppended: resulting frame model:\n");
+ parentFrame->List(stdout);
+ }
+#endif
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRangeInserted(mPresShell, aFirstNewContent, nullptr);
+ }
+#endif
+}
+
+void nsCSSFrameConstructor::ContentInserted(nsIContent* aChild,
+ InsertionKind aInsertionKind) {
+ ContentRangeInserted(aChild, aChild->GetNextSibling(), aInsertionKind);
+}
+
+// ContentRangeInserted handles creating frames for a range of nodes that
+// aren't at the end of their childlist. ContentRangeInserted isn't a real
+// content notification, but rather it handles regular ContentInserted calls
+// for a single node as well as the lazy construction of frames for a range of
+// nodes when called from CreateNeededFrames. For a range of nodes to be
+// suitable to have its frames constructed all at once they must meet the same
+// conditions that ContentAppended imposes (GetRangeInsertionPoint checks
+// these), plus more. Namely when finding the insertion prevsibling we must not
+// need to consult something specific to any one node in the range, so that the
+// insertion prevsibling would be the same for each node in the range. So we
+// pass the first node in the range to GetInsertionPrevSibling, and if
+// IsValidSibling (the only place GetInsertionPrevSibling might look at the
+// passed in node itself) needs to resolve style on the node we record this and
+// return that this range needs to be split up and inserted separately. Table
+// captions need extra attention as we need to determine where to insert them
+// in the caption list, while skipping any nodes in the range being inserted
+// (because when we treat the caption frames the other nodes have had their
+// frames constructed but not yet inserted into the frame tree).
+void nsCSSFrameConstructor::ContentRangeInserted(nsIContent* aStartChild,
+ nsIContent* aEndChild,
+ InsertionKind aInsertionKind) {
+ MOZ_ASSERT(aInsertionKind == InsertionKind::Sync ||
+ !RestyleManager()->IsInStyleRefresh());
+
+ AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ContentRangeInserted",
+ LAYOUT_FrameConstruction);
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+
+ MOZ_ASSERT(aStartChild, "must always pass a child");
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf(
+ "nsCSSFrameConstructor::ContentRangeInserted container=%p "
+ "start-child=%p end-child=%p lazy=%d\n",
+ aStartChild->GetParent(), aStartChild, aEndChild,
+ aInsertionKind == InsertionKind::Async);
+ if (gReallyNoisyContentUpdates) {
+ if (aStartChild->GetParent()) {
+ aStartChild->GetParent()->List(stdout, 0);
+ } else {
+ aStartChild->List(stdout, 0);
+ }
+ }
+ }
+
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ // XXX the GetContent() != child check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ NS_ASSERTION(
+ !child->GetPrimaryFrame() ||
+ child->GetPrimaryFrame()->GetContent() != child,
+ "asked to construct a frame for a node that already has a frame");
+ }
+#endif
+
+ bool isSingleInsert = (aStartChild->GetNextSibling() == aEndChild);
+ NS_ASSERTION(isSingleInsert || aInsertionKind == InsertionKind::Sync,
+ "range insert shouldn't be lazy");
+ NS_ASSERTION(isSingleInsert || aEndChild,
+ "range should not include all nodes after aStartChild");
+
+ // If we have a null parent, then this must be the document element being
+ // inserted, or some other child of the document in the DOM (might be a PI,
+ // say).
+ if (!aStartChild->GetParent()) {
+ MOZ_ASSERT(isSingleInsert,
+ "root node insertion should be a single insertion");
+ Element* docElement = mDocument->GetRootElement();
+ if (aStartChild != docElement) {
+ // Not the root element; just bail out
+ return;
+ }
+
+ MOZ_ASSERT(!mRootElementFrame, "root element frame already created");
+ if (aInsertionKind == InsertionKind::Async) {
+ docElement->SetFlags(NODE_NEEDS_FRAME);
+ LazilyStyleNewChildRange(docElement, nullptr);
+ return;
+ }
+
+ // Create frames for the document element and its child elements
+ if (ConstructDocElementFrame(docElement)) {
+ InvalidateCanvasIfNeeded(mPresShell, aStartChild);
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf(
+ "nsCSSFrameConstructor::ContentRangeInserted: resulting frame "
+ "model:\n");
+ mRootElementFrame->List(stdout);
+ }
+#endif
+ }
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRangeInserted(mPresShell, aStartChild, aEndChild);
+ }
+#endif
+
+ return;
+ }
+
+ InsertionPoint insertion;
+ if (isSingleInsert) {
+ // See if we have a Shadow DOM insertion point. If so, then that's our real
+ // parent frame; if not, then the frame hasn't been built yet and we just
+ // bail.
+ insertion = GetInsertionPoint(aStartChild);
+ } else {
+ // Get our insertion point. If we need to issue single ContentInserteds
+ // GetRangeInsertionPoint will take care of that for us.
+ LAYOUT_PHASE_TEMP_EXIT();
+ insertion = GetRangeInsertionPoint(aStartChild, aEndChild, aInsertionKind);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+
+ if (!insertion.mParentFrame) {
+ // We're punting on frame construction because there's no container frame.
+ // The Servo-backed style system handles this case like the lazy frame
+ // construction case, except when we're already constructing frames, in
+ // which case we shouldn't need to do anything else.
+ if (aInsertionKind == InsertionKind::Async) {
+ LazilyStyleNewChildRange(aStartChild, aEndChild);
+ }
+ return;
+ }
+
+ if (aInsertionKind == InsertionKind::Async) {
+ ConstructLazily(CONTENTINSERT, aStartChild);
+ LazilyStyleNewChildRange(aStartChild, aEndChild);
+ return;
+ }
+
+ bool isAppend, isRangeInsertSafe;
+ nsIFrame* prevSibling = GetInsertionPrevSibling(
+ &insertion, aStartChild, &isAppend, &isRangeInsertSafe);
+
+ // check if range insert is safe
+ if (!isSingleInsert && !isRangeInsertSafe) {
+ // must fall back to a single ContertInserted for each child in the range
+ LAYOUT_PHASE_TEMP_EXIT();
+ IssueSingleInsertNofications(aStartChild, aEndChild, InsertionKind::Sync);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+
+ LayoutFrameType frameType = insertion.mParentFrame->Type();
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForFrameset(insertion.mParentFrame, aStartChild,
+ aEndChild)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // We should only get here with fieldsets when doing a single insert, because
+ // fieldsets have multiple insertion points.
+ NS_ASSERTION(isSingleInsert || frameType != LayoutFrameType::FieldSet,
+ "Unexpected parent");
+ // Note that this check is insufficient if aStartChild is not a legend with
+ // display::contents that contains a legend. We'll catch that case in
+ // WipeContainingBlock. (That code would also catch this case, but handling
+ // this early is slightly faster.)
+ // XXXmats we should be able to optimize this when the fieldset doesn't
+ // currently have a rendered legend. ContentRangeInserted needs to be fixed
+ // to use the inner frame as the content insertion frame in that case.
+ if (GetFieldSetFrameFor(insertion.mParentFrame) &&
+ aStartChild->NodeInfo()->NameAtom() == nsGkAtoms::legend) {
+ // Just reframe the parent, since figuring out whether this
+ // should be the new legend and then handling it is too complex.
+ // We could do a little better here --- check if the fieldset already
+ // has a legend which occurs earlier in its child list than this node,
+ // and if so, proceed. But we'd have to extend nsFieldSetFrame
+ // to locate this legend in the inserted frames and extract it.
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(insertion.mParentFrame->GetContent(),
+ InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+
+ // Don't construct kids of leaves
+ if (insertion.mParentFrame->IsLeaf()) {
+ // Clear lazy bits so we don't try to construct again.
+ ClearLazyBits(aStartChild, aEndChild);
+ return;
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeInsertionParent(insertion.mParentFrame)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ nsFrameConstructorState state(
+ mPresShell, GetAbsoluteContainingBlock(insertion.mParentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(insertion.mParentFrame, ABS_POS),
+ GetFloatContainingBlock(insertion.mParentFrame),
+ do_AddRef(mFrameTreeState));
+
+ // Recover state for the containing block - we need to know if
+ // it has :first-letter or :first-line style applied to it. The
+ // reason we care is that the internal structure in these cases
+ // is not the normal structure and requires custom updating
+ // logic.
+ nsContainerFrame* containingBlock = state.mFloatedList.mContainingBlock;
+ bool haveFirstLetterStyle = false;
+ bool haveFirstLineStyle = false;
+
+ // In order to shave off some cycles, we only dig up the
+ // containing block haveFirst* flags if the parent frame where
+ // the insertion/append is occurring is an inline or block
+ // container. For other types of containers this isn't relevant.
+ StyleDisplayInside parentDisplayInside =
+ insertion.mParentFrame->StyleDisplay()->DisplayInside();
+
+ // Examine the insertion.mParentFrame where the insertion is taking
+ // place. If it's a certain kind of container then some special
+ // processing is done.
+ if (StyleDisplayInside::Flow == parentDisplayInside) {
+ // Recover the special style flags for the containing block
+ if (containingBlock) {
+ haveFirstLetterStyle = HasFirstLetterStyle(containingBlock);
+ haveFirstLineStyle = ShouldHaveFirstLineStyle(
+ containingBlock->GetContent(), containingBlock->Style());
+ }
+
+ if (haveFirstLetterStyle) {
+ // If our current insertion.mParentFrame is a Letter frame, use its parent
+ // as our new parent hint
+ if (insertion.mParentFrame->IsLetterFrame()) {
+ // If insertion.mParentFrame is out of flow, then we actually want the
+ // parent of the placeholder frame.
+ if (insertion.mParentFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ nsPlaceholderFrame* placeholderFrame =
+ insertion.mParentFrame->GetPlaceholderFrame();
+ NS_ASSERTION(placeholderFrame, "No placeholder for out-of-flow?");
+ insertion.mParentFrame = placeholderFrame->GetParent();
+ } else {
+ insertion.mParentFrame = insertion.mParentFrame->GetParent();
+ }
+ }
+
+ // Remove the old letter frames before doing the insertion
+ RemoveLetterFrames(mPresShell, state.mFloatedList.mContainingBlock);
+
+ // Removing the letterframes messes around with the frame tree, removing
+ // and creating frames. We need to reget our prevsibling, parent frame,
+ // etc.
+ prevSibling = GetInsertionPrevSibling(&insertion, aStartChild, &isAppend,
+ &isRangeInsertSafe);
+
+ // Need check whether a range insert is still safe.
+ if (!isSingleInsert && !isRangeInsertSafe) {
+ // Need to recover the letter frames first.
+ RecoverLetterFrames(state.mFloatedList.mContainingBlock);
+
+ // must fall back to a single ContertInserted for each child in the
+ // range
+ LAYOUT_PHASE_TEMP_EXIT();
+ IssueSingleInsertNofications(aStartChild, aEndChild,
+ InsertionKind::Sync);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+
+ frameType = insertion.mParentFrame->Type();
+ }
+ }
+
+ // This handles fallback to 'list-style-type' when a 'list-style-image' fails
+ // to load.
+ if (aStartChild->IsInNativeAnonymousSubtree() &&
+ aStartChild->IsHTMLElement(nsGkAtoms::mozgeneratedcontentimage)) {
+ MOZ_ASSERT(isSingleInsert);
+ MOZ_ASSERT(insertion.mParentFrame->Style()->GetPseudoType() ==
+ PseudoStyleType::marker,
+ "we can only handle ::marker fallback for now");
+ nsIContent* const nextSibling = aStartChild->GetNextSibling();
+ MOZ_ASSERT(nextSibling && nextSibling->IsText(),
+ "expected a text node after the list-style-image image");
+ DestroyContext context(mPresShell);
+ RemoveFrame(context, FrameChildListID::Principal,
+ nextSibling->GetPrimaryFrame());
+ auto* const container = aStartChild->GetParent()->AsElement();
+ nsIContent* firstNewChild = nullptr;
+ auto InsertChild = [this, container, nextSibling,
+ &firstNewChild](RefPtr<nsIContent>&& aChild) {
+ // We don't strictly have to set NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE
+ // here; it would get set under AppendChildTo. But AppendChildTo might
+ // think that we're going from not being anonymous to being anonymous and
+ // do some extra work; setting the flag here avoids that.
+ aChild->SetFlags(NODE_IS_IN_NATIVE_ANONYMOUS_SUBTREE);
+ container->InsertChildBefore(aChild, nextSibling, false, IgnoreErrors());
+ if (auto* childElement = Element::FromNode(aChild)) {
+ // If we created any children elements, Servo needs to traverse them,
+ // but the root is already set up.
+ mPresShell->StyleSet()->StyleNewSubtree(childElement);
+ }
+ if (!firstNewChild) {
+ firstNewChild = aChild;
+ }
+ };
+ CreateGeneratedContentFromListStyleType(
+ state, *insertion.mContainer->AsElement(),
+ *insertion.mParentFrame->Style(), InsertChild);
+ if (!firstNewChild) {
+ // No fallback content - we're done.
+ return;
+ }
+ aStartChild = firstNewChild;
+ MOZ_ASSERT(firstNewChild->GetNextSibling() == nextSibling,
+ "list-style-type should only create one child");
+ }
+
+ AutoFrameConstructionItemList items(this);
+ RefPtr<ComputedStyle> parentStyle =
+ ResolveComputedStyle(insertion.mContainer);
+ ParentType parentType = GetParentType(frameType);
+ FlattenedChildIterator iter(insertion.mContainer);
+ const bool haveNoShadowDOM =
+ !iter.ShadowDOMInvolved() || !iter.GetNextChild();
+ if (aStartChild->GetPreviousSibling() && parentType == eTypeBlock &&
+ haveNoShadowDOM) {
+ // If there's a text node in the normal content list just before the
+ // new nodes, and it has no frame, make a frame construction item for
+ // it, because it might need a frame now. No need to do this if our
+ // parent type is not block, though, since WipeContainingBlock
+ // already handles that situation.
+ AddTextItemIfNeeded(state, *parentStyle, insertion,
+ aStartChild->GetPreviousSibling(), items);
+ }
+
+ if (isSingleInsert) {
+ AddFrameConstructionItems(state, aStartChild,
+ aStartChild->IsRootOfNativeAnonymousSubtree(),
+ *parentStyle, insertion, items);
+ } else {
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ AddFrameConstructionItems(state, child, false, *parentStyle, insertion,
+ items);
+ }
+ }
+
+ if (aEndChild && parentType == eTypeBlock && haveNoShadowDOM) {
+ // If there's a text node in the normal content list just after the
+ // new nodes, and it has no frame, make a frame construction item for
+ // it, because it might need a frame now. No need to do this if our
+ // parent type is not block, though, since WipeContainingBlock
+ // already handles that situation.
+ AddTextItemIfNeeded(state, *parentStyle, insertion, aEndChild, items);
+ }
+
+ // Perform special check for diddling around with the frames in
+ // a special inline frame.
+ // If we're appending before :after content, then we're not really
+ // appending, so let WipeContainingBlock know that.
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (WipeContainingBlock(state, containingBlock, insertion.mParentFrame, items,
+ isAppend, prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ nsFrameConstructorSaveState floatSaveState;
+ state.MaybePushFloatContainingBlock(insertion.mParentFrame, floatSaveState);
+
+ if (state.mPresContext->IsPaginated()) {
+ // Because this function can be called outside frame construction, we need
+ // to set state.mAutoPageNameValue based on what the parent frame's auto
+ // value is.
+ // Calling this from outside the frame constructor can violate many of the
+ // expectations in AutoFrameConstructionPageName, and unlike during frame
+ // construction we already have an auto value from parentFrame, so we do
+ // not use AutoFrameConstructionPageName here.
+ state.mAutoPageNameValue = insertion.mParentFrame->GetAutoPageValue();
+#ifdef DEBUG
+ insertion.mParentFrame->mWasVisitedByAutoFrameConstructionPageName = true;
+#endif
+ }
+
+ // If the container is a table and a caption will be appended, it needs to be
+ // put in the table wrapper frame's additional child list.
+ // We make no attempt here to set flags to indicate whether the list
+ // will be at the start or end of a block. It doesn't seem worthwhile.
+ nsFrameList frameList, captionList;
+ ConstructFramesFromItemList(state, items, insertion.mParentFrame,
+ ParentIsWrapperAnonBox(insertion.mParentFrame),
+ frameList);
+
+ if (frameList.NotEmpty()) {
+ for (nsIContent* child = aStartChild; child != aEndChild;
+ child = child->GetNextSibling()) {
+ InvalidateCanvasIfNeeded(mPresShell, child);
+ }
+
+ if (LayoutFrameType::Table == frameType ||
+ LayoutFrameType::TableWrapper == frameType) {
+ PullOutCaptionFrames(frameList, captionList);
+ }
+ }
+
+ if (haveFirstLineStyle && insertion.mParentFrame == containingBlock &&
+ isAppend) {
+ // It's possible that the new frame goes into a first-line
+ // frame. Look at it and see...
+ AppendFirstLineFrames(state, containingBlock->GetContent(), containingBlock,
+ frameList);
+ } else if (insertion.mParentFrame->Style()->HasPseudoElementData()) {
+ CheckForFirstLineInsertion(insertion.mParentFrame, frameList);
+ CheckForFirstLineInsertion(insertion.mParentFrame, captionList);
+ }
+
+ // We might have captions; put them into the caption list of the
+ // table wrapper frame.
+ if (captionList.NotEmpty()) {
+ NS_ASSERTION(LayoutFrameType::Table == frameType ||
+ LayoutFrameType::TableWrapper == frameType,
+ "parent for caption is not table?");
+ // We need to determine where to put the caption items; start with the
+ // the parent frame that has already been determined and get the insertion
+ // prevsibling of the first caption item.
+ bool captionIsAppend;
+ nsIFrame* captionPrevSibling = nullptr;
+
+ // aIsRangeInsertSafe is ignored on purpose because it is irrelevant here.
+ bool ignored;
+ InsertionPoint captionInsertion = insertion;
+ if (isSingleInsert) {
+ captionPrevSibling = GetInsertionPrevSibling(
+ &captionInsertion, aStartChild, &captionIsAppend, &ignored);
+ } else {
+ nsIContent* firstCaption = captionList.FirstChild()->GetContent();
+ // It is very important here that we skip the children in
+ // [aStartChild,aEndChild) when looking for a
+ // prevsibling.
+ captionPrevSibling = GetInsertionPrevSibling(
+ &captionInsertion, firstCaption, &captionIsAppend, &ignored,
+ aStartChild, aEndChild);
+ }
+
+ nsContainerFrame* outerTable =
+ captionInsertion.mParentFrame->IsTableFrame()
+ ? captionInsertion.mParentFrame->GetParent()
+ : captionInsertion.mParentFrame;
+
+ // If the parent is not a table wrapper frame we will try to add frames
+ // to a named child list that the parent does not honor and the frames
+ // will get lost.
+ MOZ_ASSERT(outerTable->IsTableWrapperFrame(),
+ "Pseudo frame construction failure; "
+ "a caption can be only a child of a table wrapper frame");
+
+ // If the parent of our current prevSibling is different from the frame
+ // we'll actually use as the parent, then the calculated insertion
+ // point is now invalid (bug 341382).
+ if (captionPrevSibling && captionPrevSibling->GetParent() != outerTable) {
+ captionPrevSibling = nullptr;
+ }
+
+ captionList.ApplySetParent(outerTable);
+ if (captionIsAppend) {
+ AppendFrames(outerTable, FrameChildListID::Caption,
+ std::move(captionList));
+ } else {
+ InsertFrames(outerTable, FrameChildListID::Caption, captionPrevSibling,
+ std::move(captionList));
+ }
+ }
+
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateForColumnSpan(state, insertion.mParentFrame, frameList,
+ prevSibling)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ if (frameList.NotEmpty()) {
+ // Notify the parent frame
+ if (isAppend) {
+ AppendFramesToParent(state, insertion.mParentFrame, frameList,
+ prevSibling);
+ } else {
+ InsertFrames(insertion.mParentFrame, FrameChildListID::Principal,
+ prevSibling, std::move(frameList));
+ }
+ }
+
+ if (haveFirstLetterStyle) {
+ // Recover the letter frames for the containing block when
+ // it has first-letter style.
+ RecoverLetterFrames(state.mFloatedList.mContainingBlock);
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates && insertion.mParentFrame) {
+ printf(
+ "nsCSSFrameConstructor::ContentRangeInserted: resulting frame "
+ "model:\n");
+ insertion.mParentFrame->List(stdout);
+ }
+#endif
+
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRangeInserted(mPresShell, aStartChild, aEndChild);
+ }
+#endif
+}
+
+bool nsCSSFrameConstructor::ContentRemoved(nsIContent* aChild,
+ nsIContent* aOldNextSibling,
+ RemoveFlags aFlags) {
+ MOZ_ASSERT(aChild);
+ MOZ_ASSERT(!aChild->IsRootOfNativeAnonymousSubtree() || !aOldNextSibling,
+ "Anonymous roots don't have siblings");
+ AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::ContentRemoved",
+ LAYOUT_FrameConstruction);
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+ nsPresContext* presContext = mPresShell->GetPresContext();
+ MOZ_ASSERT(presContext, "Our presShell should have a valid presContext");
+
+ // We want to detect when the viewport override element stored in the
+ // prescontext is in the subtree being removed. Except in fullscreen cases
+ // (which are handled in Element::UnbindFromTree and do not get stored on the
+ // prescontext), the override element is always either the root element or a
+ // <body> child of the root element. So we can only be removing the stored
+ // override element if the thing being removed is either the override element
+ // itself or the root element (which can be a parent of the override element).
+ if (aChild == presContext->GetViewportScrollStylesOverrideElement() ||
+ (aChild->IsElement() && !aChild->GetParent())) {
+ // We might be removing the element that we propagated viewport scrollbar
+ // styles from. Recompute those. (This clause covers two of the three
+ // possible scrollbar-propagation sources: the <body> [as aChild or a
+ // descendant] and the root node. The other possible scrollbar-propagation
+ // source is a fullscreen element, and we have code elsewhere to update
+ // scrollbars after fullscreen elements are removed -- specifically, it's
+ // part of the fullscreen cleanup code called by Element::UnbindFromTree.
+ // We don't handle the fullscreen case here, because it doesn't change the
+ // scrollbar styles override element stored on the prescontext.)
+ Element* newOverrideElement =
+ presContext->UpdateViewportScrollStylesOverride();
+
+ // If aChild is the root, then we don't need to do any reframing of
+ // newOverrideElement, because we're about to tear down the whole frame tree
+ // anyway. And we need to make sure we don't do any such reframing, because
+ // reframing the <body> can trigger a reframe of the <html> and then reenter
+ // here.
+ //
+ // But if aChild is not the root, and if newOverrideElement is not
+ // the root and isn't aChild (which it could be if all we're doing
+ // here is reframing the current override element), it needs
+ // reframing. In particular, it used to have a scrollframe
+ // (because its overflow was not "visible"), but now it will
+ // propagate its overflow to the viewport, so it should not need a
+ // scrollframe anymore.
+ if (aChild->GetParent() && newOverrideElement &&
+ newOverrideElement->GetParent() && newOverrideElement != aChild) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(newOverrideElement, InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+ }
+
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf(
+ "nsCSSFrameConstructor::ContentRemoved container=%p child=%p "
+ "old-next-sibling=%p\n",
+ aChild->GetParent(), aChild, aOldNextSibling);
+ if (gReallyNoisyContentUpdates) {
+ aChild->GetParent()->List(stdout, 0);
+ }
+ }
+#endif
+
+ nsIFrame* childFrame = aChild->GetPrimaryFrame();
+ if (!childFrame || childFrame->GetContent() != aChild) {
+ // XXXbz the GetContent() != aChild check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ childFrame = nullptr;
+ }
+
+ // If we're removing the root, then make sure to remove things starting at
+ // the viewport's child instead of the primary frame (which might even be
+ // null if the root was display:none, even though the frames above it got
+ // created). Detecting removal of a root is a little exciting; in particular,
+ // having no parent is necessary but NOT sufficient.
+ //
+ // Due to how we process reframes, the content node might not even be in our
+ // document by now. So explicitly check whether the viewport's first kid's
+ // content node is aChild.
+ //
+ // FIXME(emilio): I think the "might not be in our document" bit is impossible
+ // now.
+ bool isRoot = false;
+ if (!aChild->GetParent()) {
+ if (nsIFrame* viewport = GetRootFrame()) {
+ nsIFrame* firstChild = viewport->PrincipalChildList().FirstChild();
+ if (firstChild && firstChild->GetContent() == aChild) {
+ isRoot = true;
+ childFrame = firstChild;
+ NS_ASSERTION(!childFrame->GetNextSibling(), "How did that happen?");
+ }
+ }
+ }
+
+ // We need to be conservative about when to determine whether something has
+ // display: contents or not because at this point our actual display may be
+ // different.
+ //
+ // Consider the case of:
+ //
+ // <div id="A" style="display: contents"><div id="B"></div></div>
+ //
+ // If we reconstruct A because its display changed to "none", we still need to
+ // cleanup the frame on B, but A's display is now "none", so we can't poke at
+ // the style of it.
+ //
+ // FIXME(emilio, bug 1450366): We can make this faster without adding much
+ // complexity for the display: none -> other case, which right now
+ // unnecessarily walks the content tree down.
+ auto CouldHaveBeenDisplayContents = [aFlags](nsIContent* aContent) -> bool {
+ return aFlags == REMOVE_FOR_RECONSTRUCTION || IsDisplayContents(aContent);
+ };
+
+ if (!childFrame && CouldHaveBeenDisplayContents(aChild)) {
+ // NOTE(emilio): We may iterate through ::before and ::after here and they
+ // may be gone after the respective ContentRemoved call. Right now
+ // StyleChildrenIterator handles that properly, so it's not an issue.
+ StyleChildrenIterator iter(aChild);
+ for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
+ if (c->GetPrimaryFrame() || CouldHaveBeenDisplayContents(c)) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ bool didReconstruct = ContentRemoved(c, nullptr, aFlags);
+ LAYOUT_PHASE_TEMP_REENTER();
+ if (didReconstruct) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ if (childFrame) {
+ if (aFlags == REMOVE_FOR_RECONSTRUCTION) {
+ // Before removing the frames associated with the content object,
+ // ask them to save their state onto our state object.
+ CaptureStateForFramesOf(aChild, mFrameTreeState);
+ }
+
+ InvalidateCanvasIfNeeded(mPresShell, aChild);
+
+ // See whether we need to remove more than just childFrame
+ LAYOUT_PHASE_TEMP_EXIT();
+ if (MaybeRecreateContainerForFrameRemoval(childFrame)) {
+ LAYOUT_PHASE_TEMP_REENTER();
+ return true;
+ }
+ LAYOUT_PHASE_TEMP_REENTER();
+
+ // Get the childFrame's parent frame
+ nsIFrame* parentFrame = childFrame->GetParent();
+ LayoutFrameType parentType = parentFrame->Type();
+
+ if (parentType == LayoutFrameType::FrameSet &&
+ IsSpecialFramesetChild(aChild)) {
+ // Just reframe the parent, since framesets are weird like that.
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(parentFrame->GetContent(), InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return true;
+ }
+
+ // If we're a child of MathML, then we should reframe the MathML content.
+ // If we're non-MathML, then we would be wrapped in a block so we need to
+ // check our grandparent in that case.
+ nsIFrame* possibleMathMLAncestor = parentType == LayoutFrameType::Block
+ ? parentFrame->GetParent()
+ : parentFrame;
+ if (possibleMathMLAncestor->IsMathMLFrame()) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(parentFrame->GetContent(), InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return true;
+ }
+
+#ifdef ACCESSIBILITY
+ if (aFlags != REMOVE_FOR_RECONSTRUCTION) {
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->ContentRemoved(mPresShell, aChild);
+ }
+ }
+#endif
+
+ // Examine the containing-block for the removed content and see if
+ // :first-letter style applies.
+ nsIFrame* inflowChild = childFrame;
+ if (childFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ inflowChild = childFrame->GetPlaceholderFrame();
+ NS_ASSERTION(inflowChild, "No placeholder for out-of-flow?");
+ }
+ nsContainerFrame* containingBlock =
+ GetFloatContainingBlock(inflowChild->GetParent());
+ bool haveFLS = containingBlock && HasFirstLetterStyle(containingBlock);
+ if (haveFLS) {
+ // Trap out to special routine that handles adjusting a blocks
+ // frame tree when first-letter style is present.
+#ifdef NOISY_FIRST_LETTER
+ printf("ContentRemoved: containingBlock=");
+ containingBlock->ListTag(stdout);
+ printf(" parentFrame=");
+ parentFrame->ListTag(stdout);
+ printf(" childFrame=");
+ childFrame->ListTag(stdout);
+ printf("\n");
+#endif
+
+ // First update the containing blocks structure by removing the
+ // existing letter frames. This makes the subsequent logic
+ // simpler.
+ RemoveLetterFrames(mPresShell, containingBlock);
+
+ // Recover childFrame and parentFrame
+ childFrame = aChild->GetPrimaryFrame();
+ if (!childFrame || childFrame->GetContent() != aChild) {
+ // XXXbz the GetContent() != aChild check is needed due to bug 135040.
+ // Remove it once that's fixed.
+ return false;
+ }
+ parentFrame = childFrame->GetParent();
+ parentType = parentFrame->Type();
+
+#ifdef NOISY_FIRST_LETTER
+ printf(" ==> revised parentFrame=");
+ parentFrame->ListTag(stdout);
+ printf(" childFrame=");
+ childFrame->ListTag(stdout);
+ printf("\n");
+#endif
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates) {
+ printf("nsCSSFrameConstructor::ContentRemoved: childFrame=");
+ childFrame->ListTag(stdout);
+ putchar('\n');
+ parentFrame->List(stdout);
+ }
+#endif
+
+ // Notify the parent frame that it should delete the frame
+ if (childFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ childFrame = childFrame->GetPlaceholderFrame();
+ NS_ASSERTION(childFrame, "Missing placeholder frame for out of flow.");
+ parentFrame = childFrame->GetParent();
+ }
+
+ DestroyContext context(mPresShell);
+ RemoveFrame(context, nsLayoutUtils::GetChildListNameFor(childFrame),
+ childFrame);
+
+ // NOTE(emilio): aChild could be dead here already if it is a ::before or
+ // ::after pseudo-element (since in that case it was owned by childFrame,
+ // which we just destroyed).
+
+ if (isRoot) {
+ mRootElementFrame = nullptr;
+ mRootElementStyleFrame = nullptr;
+ mDocElementContainingBlock = nullptr;
+ mCanvasFrame = nullptr;
+ mPageSequenceFrame = nullptr;
+ }
+
+ if (haveFLS && mRootElementFrame) {
+ RecoverLetterFrames(containingBlock);
+ }
+
+ // If we're just reconstructing frames for the element, then the
+ // following ContentInserted notification on the element will
+ // take care of fixing up any adjacent text nodes. We don't need
+ // to do this if the table parent type of our parent type is not
+ // eTypeBlock, though, because in that case the whitespace isn't
+ // being suppressed due to us anyway.
+ if (aOldNextSibling && aFlags == REMOVE_CONTENT &&
+ GetParentType(parentType) == eTypeBlock) {
+ MOZ_ASSERT(aChild->GetParentNode(),
+ "How did we have a sibling without a parent?");
+ // Adjacent whitespace-only text nodes might have been suppressed if
+ // this node does not have inline ends. Create frames for them now
+ // if necessary.
+ // Reframe any text node just before the node being removed, if there is
+ // one, and if it's not the last child or the first child. If a whitespace
+ // textframe was being suppressed and it's now the last child or first
+ // child then it can stay suppressed since the parent must be a block
+ // and hence it's adjacent to a block end.
+ // If aOldNextSibling is null, then the text node before the node being
+ // removed is the last node, and we don't need to worry about it.
+ //
+ // FIXME(emilio): This should probably use the lazy frame construction
+ // bits if possible instead of reframing it in place.
+ nsIContent* prevSibling = aOldNextSibling->GetPreviousSibling();
+ if (prevSibling && prevSibling->GetPreviousSibling()) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ ReframeTextIfNeeded(prevSibling);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+ // Reframe any text node just after the node being removed, if there is
+ // one, and if it's not the last child or the first child.
+ if (aOldNextSibling->GetNextSibling() &&
+ aOldNextSibling->GetPreviousSibling()) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ ReframeTextIfNeeded(aOldNextSibling);
+ LAYOUT_PHASE_TEMP_REENTER();
+ }
+ }
+
+#ifdef DEBUG
+ if (gReallyNoisyContentUpdates && parentFrame) {
+ printf("nsCSSFrameConstructor::ContentRemoved: resulting frame model:\n");
+ parentFrame->List(stdout);
+ }
+#endif
+ }
+
+ return false;
+}
+
+/**
+ * This method invalidates the canvas when frames are removed or added for a
+ * node that might have its background propagated to the canvas, i.e., a
+ * document root node or an HTML BODY which is a child of the root node.
+ *
+ * @param aFrame a frame for a content node about to be removed or a frame that
+ * was just created for a content node that was inserted.
+ */
+static void InvalidateCanvasIfNeeded(PresShell* aPresShell, nsIContent* aNode) {
+ MOZ_ASSERT(aPresShell->GetRootFrame(), "What happened here?");
+ MOZ_ASSERT(aPresShell->GetPresContext(), "Say what?");
+
+ // Note that both in ContentRemoved and ContentInserted the content node
+ // will still have the right parent pointer, so looking at that is ok.
+
+ nsIContent* parent = aNode->GetParent();
+ if (parent) {
+ // Has a parent; might not be what we want
+ nsIContent* grandParent = parent->GetParent();
+ if (grandParent) {
+ // Has a grandparent, so not what we want
+ return;
+ }
+
+ // Check whether it's an HTML body
+ if (!aNode->IsHTMLElement(nsGkAtoms::body)) {
+ return;
+ }
+ }
+
+ // At this point the node has no parent or it's an HTML <body> child of the
+ // root. We might not need to invalidate in this case (eg we might be in
+ // XHTML or something), but chances are we want to. Play it safe.
+ // Invalidate the viewport.
+
+ nsIFrame* rootFrame = aPresShell->GetRootFrame();
+ rootFrame->InvalidateFrameSubtree();
+}
+
+bool nsCSSFrameConstructor::EnsureFrameForTextNodeIsCreatedAfterFlush(
+ CharacterData* aContent) {
+ if (!aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE)) {
+ return false;
+ }
+
+ if (mAlwaysCreateFramesForIgnorableWhitespace) {
+ return false;
+ }
+
+ // Text frame may have been suppressed. Disable suppression and signal that a
+ // flush should be performed. We do this on a document-wide basis so that
+ // pages that repeatedly query metrics for collapsed-whitespace text nodes
+ // don't trigger pathological behavior.
+ mAlwaysCreateFramesForIgnorableWhitespace = true;
+ Element* root = mDocument->GetRootElement();
+ if (!root) {
+ return false;
+ }
+
+ RestyleManager()->PostRestyleEvent(root, RestyleHint{0},
+ nsChangeHint_ReconstructFrame);
+ return true;
+}
+
+void nsCSSFrameConstructor::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo& aInfo) {
+ AUTO_PROFILER_LABEL_HOT("nsCSSFrameConstructor::CharacterDataChanged",
+ LAYOUT_FrameConstruction);
+ AUTO_LAYOUT_PHASE_ENTRY_POINT(mPresShell->GetPresContext(), FrameC);
+
+ if ((aContent->HasFlag(NS_CREATE_FRAME_IF_NON_WHITESPACE) &&
+ !aContent->TextIsOnlyWhitespace()) ||
+ (aContent->HasFlag(NS_REFRAME_IF_WHITESPACE) &&
+ aContent->TextIsOnlyWhitespace())) {
+#ifdef DEBUG
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ NS_ASSERTION(!frame || !frame->IsGeneratedContentFrame(),
+ "Bit should never be set on generated content");
+#endif
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(aContent, InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+
+ // It's possible the frame whose content changed isn't inserted into the
+ // frame hierarchy yet, or that there is no frame that maps the content
+ if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
+#if 0
+ NS_FRAME_LOG(NS_FRAME_TRACE_CALLS,
+ ("nsCSSFrameConstructor::CharacterDataChanged: content=%p[%s] subcontent=%p frame=%p",
+ aContent, ContentTag(aContent, 0),
+ aSubContent, frame));
+#endif
+
+ if (frame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
+ LAYOUT_PHASE_TEMP_EXIT();
+ RecreateFramesForContent(aContent, InsertionKind::Async);
+ LAYOUT_PHASE_TEMP_REENTER();
+ return;
+ }
+
+ // Special check for text content that is a child of a letter frame. If
+ // this happens, we should remove the letter frame, do whatever we're
+ // planning to do with this notification, then put the letter frame back.
+ // Note that this is basically what RecreateFramesForContent ends up doing;
+ // the reason we dont' want to call that here is that our text content
+ // could be native anonymous, in which case RecreateFramesForContent would
+ // completely barf on it. And recreating the non-anonymous ancestor would
+ // just lead us to come back into this notification (e.g. if quotes or
+ // counters are involved), leading to a loop.
+ nsContainerFrame* block = GetFloatContainingBlock(frame);
+ bool haveFirstLetterStyle = false;
+ if (block) {
+ // See if the block has first-letter style applied to it.
+ haveFirstLetterStyle = HasFirstLetterStyle(block);
+ if (haveFirstLetterStyle) {
+ RemoveLetterFrames(mPresShell, block);
+ // Reget |frame|, since we might have killed it.
+ // Do we really need to call CharacterDataChanged in this case, though?
+ frame = aContent->GetPrimaryFrame();
+ NS_ASSERTION(frame, "Should have frame here!");
+ }
+ }
+
+ // Notify the first frame that maps the content. It will generate a reflow
+ // command
+ frame->CharacterDataChanged(aInfo);
+
+ if (haveFirstLetterStyle) {
+ RecoverLetterFrames(block);
+ }
+ }
+}
+
+void nsCSSFrameConstructor::RecalcQuotesAndCounters() {
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mQuotesDirty) {
+ mQuotesDirty = false;
+ mContainStyleScopeManager.RecalcAllQuotes();
+ }
+
+ if (mCountersDirty) {
+ mCountersDirty = false;
+ mContainStyleScopeManager.RecalcAllCounters();
+ }
+
+ NS_ASSERTION(!mQuotesDirty, "Quotes updates will be lost");
+ NS_ASSERTION(!mCountersDirty, "Counter updates will be lost");
+}
+
+void nsCSSFrameConstructor::NotifyCounterStylesAreDirty() {
+ mContainStyleScopeManager.SetAllCountersDirty();
+ CountersDirty();
+}
+
+void nsCSSFrameConstructor::WillDestroyFrameTree() {
+#if defined(DEBUG_dbaron_off)
+ mContainStyleScopeManager.DumpCounters();
+#endif
+
+ // Prevent frame tree destruction from being O(N^2)
+ mContainStyleScopeManager.Clear();
+ nsFrameManager::Destroy();
+}
+
+// STATIC
+
+// XXXbz I'd really like this method to go away. Once we have inline-block and
+// I can just use that for sized broken images, that can happen, maybe.
+//
+// NOTE(emilio): This needs to match MozAltContent handling.
+void nsCSSFrameConstructor::GetAlternateTextFor(const Element& aElement,
+ nsAString& aAltText) {
+ // The "alt" attribute specifies alternate text that is rendered
+ // when the image can not be displayed.
+ if (aElement.GetAttr(nsGkAtoms::alt, aAltText)) {
+ return;
+ }
+
+ if (aElement.IsHTMLElement(nsGkAtoms::input)) {
+ // If there's no "alt" attribute, and aElement is an input element, then use
+ // the value of the "value" attribute.
+ if (aElement.GetAttr(nsGkAtoms::value, aAltText)) {
+ return;
+ }
+
+ // If there's no "value" attribute either, then use the localized string for
+ // "Submit" as the alternate text.
+ nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+ "Submit", aElement.OwnerDoc(),
+ aAltText);
+ }
+}
+
+nsIFrame* nsCSSFrameConstructor::CreateContinuingOuterTableFrame(
+ nsIFrame* aFrame, nsContainerFrame* aParentFrame, nsIContent* aContent,
+ ComputedStyle* aComputedStyle) {
+ nsTableWrapperFrame* newFrame =
+ NS_NewTableWrapperFrame(mPresShell, aComputedStyle);
+
+ newFrame->Init(aContent, aParentFrame, aFrame);
+
+ // Create a continuing inner table frame, and if there's a caption then
+ // replicate the caption
+ nsFrameList newChildFrames;
+
+ nsIFrame* childFrame = aFrame->PrincipalChildList().FirstChild();
+ if (childFrame) {
+ nsIFrame* continuingTableFrame =
+ CreateContinuingFrame(childFrame, newFrame);
+ newChildFrames.AppendFrame(nullptr, continuingTableFrame);
+
+ NS_ASSERTION(!childFrame->GetNextSibling(),
+ "there can be only one inner table frame");
+ }
+
+ // Set the table wrapper's initial child list
+ newFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(newChildFrames));
+
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::CreateContinuingTableFrame(
+ nsIFrame* aFrame, nsContainerFrame* aParentFrame, nsIContent* aContent,
+ ComputedStyle* aComputedStyle) {
+ nsTableFrame* newFrame = NS_NewTableFrame(mPresShell, aComputedStyle);
+
+ newFrame->Init(aContent, aParentFrame, aFrame);
+
+ // Replicate any header/footer frames
+ nsFrameList childFrames;
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ // See if it's a header/footer, possibly wrapped in a scroll frame.
+ nsTableRowGroupFrame* rowGroupFrame =
+ static_cast<nsTableRowGroupFrame*>(childFrame);
+ // If the row group was continued, then don't replicate it.
+ nsIFrame* rgNextInFlow = rowGroupFrame->GetNextInFlow();
+ if (rgNextInFlow) {
+ rowGroupFrame->SetRepeatable(false);
+ } else if (rowGroupFrame->IsRepeatable()) {
+ // Replicate the header/footer frame.
+ nsTableRowGroupFrame* headerFooterFrame;
+ nsFrameList childList;
+
+ nsFrameConstructorState state(
+ mPresShell, GetAbsoluteContainingBlock(newFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(newFrame, ABS_POS), nullptr);
+ state.mCreatingExtraFrames = true;
+
+ ComputedStyle* const headerFooterComputedStyle = rowGroupFrame->Style();
+ headerFooterFrame = static_cast<nsTableRowGroupFrame*>(
+ NS_NewTableRowGroupFrame(mPresShell, headerFooterComputedStyle));
+
+ nsIContent* headerFooter = rowGroupFrame->GetContent();
+ headerFooterFrame->Init(headerFooter, newFrame, nullptr);
+
+ nsFrameConstructorSaveState absoluteSaveState;
+ MakeTablePartAbsoluteContainingBlock(state, absoluteSaveState,
+ headerFooterFrame);
+
+ nsFrameConstructorSaveState floatSaveState;
+ state.MaybePushFloatContainingBlock(headerFooterFrame, floatSaveState);
+
+ ProcessChildren(state, headerFooter, rowGroupFrame->Style(),
+ headerFooterFrame, true, childList, false, nullptr);
+ NS_ASSERTION(state.mFloatedList.IsEmpty(), "unexpected floated element");
+ headerFooterFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ headerFooterFrame->SetRepeatable(true);
+
+ // Table specific initialization
+ headerFooterFrame->InitRepeatedFrame(rowGroupFrame);
+
+ // XXX Deal with absolute and fixed frames...
+ childFrames.AppendFrame(nullptr, headerFooterFrame);
+ }
+ }
+
+ // Set the table frame's initial child list
+ newFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childFrames));
+
+ return newFrame;
+}
+
+nsIFrame* nsCSSFrameConstructor::CreateContinuingFrame(
+ nsIFrame* aFrame, nsContainerFrame* aParentFrame, bool aIsFluid) {
+ ComputedStyle* computedStyle = aFrame->Style();
+ nsIFrame* newFrame = nullptr;
+ nsIFrame* nextContinuation = aFrame->GetNextContinuation();
+ nsIFrame* nextInFlow = aFrame->GetNextInFlow();
+
+ // Use the frame type to determine what type of frame to create
+ LayoutFrameType frameType = aFrame->Type();
+ nsIContent* content = aFrame->GetContent();
+
+ if (LayoutFrameType::Text == frameType) {
+ newFrame = NS_NewContinuingTextFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Inline == frameType) {
+ newFrame = NS_NewInlineFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Block == frameType) {
+ MOZ_ASSERT(!aFrame->IsTableCaption(),
+ "no support for fragmenting table captions yet");
+ newFrame = NS_NewBlockFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::ColumnSetWrapper == frameType) {
+ newFrame =
+ NS_NewColumnSetWrapperFrame(mPresShell, computedStyle, nsFrameState(0));
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::ColumnSet == frameType) {
+ MOZ_ASSERT(!aFrame->IsTableCaption(),
+ "no support for fragmenting table captions yet");
+ newFrame = NS_NewColumnSetFrame(mPresShell, computedStyle, nsFrameState(0));
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::PrintedSheet == frameType) {
+ newFrame = ConstructPrintedSheetFrame(mPresShell, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Page == frameType) {
+ nsCanvasFrame* canvasFrame; // (unused outparam for ConstructPageFrame)
+ newFrame =
+ ConstructPageFrame(mPresShell, aParentFrame, aFrame, canvasFrame);
+ } else if (LayoutFrameType::TableWrapper == frameType) {
+ newFrame = CreateContinuingOuterTableFrame(aFrame, aParentFrame, content,
+ computedStyle);
+ } else if (LayoutFrameType::Table == frameType) {
+ newFrame = CreateContinuingTableFrame(aFrame, aParentFrame, content,
+ computedStyle);
+ } else if (LayoutFrameType::TableRowGroup == frameType) {
+ newFrame = NS_NewTableRowGroupFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::TableRow == frameType) {
+ nsTableRowFrame* rowFrame = NS_NewTableRowFrame(mPresShell, computedStyle);
+
+ rowFrame->Init(content, aParentFrame, aFrame);
+
+ // Create a continuing frame for each table cell frame
+ nsFrameList newChildList;
+ nsIFrame* cellFrame = aFrame->PrincipalChildList().FirstChild();
+ while (cellFrame) {
+ // See if it's a table cell frame
+ if (cellFrame->IsTableCellFrame()) {
+ nsIFrame* continuingCellFrame =
+ CreateContinuingFrame(cellFrame, rowFrame);
+ newChildList.AppendFrame(nullptr, continuingCellFrame);
+ }
+ cellFrame = cellFrame->GetNextSibling();
+ }
+
+ rowFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(newChildList));
+ newFrame = rowFrame;
+
+ } else if (LayoutFrameType::TableCell == frameType) {
+ // Warning: If you change this and add a wrapper frame around table cell
+ // frames, make sure Bug 368554 doesn't regress!
+ // See IsInAutoWidthTableCellForQuirk() in nsImageFrame.cpp.
+ nsTableFrame* tableFrame =
+ static_cast<nsTableRowFrame*>(aParentFrame)->GetTableFrame();
+ nsTableCellFrame* cellFrame =
+ NS_NewTableCellFrame(mPresShell, computedStyle, tableFrame);
+
+ cellFrame->Init(content, aParentFrame, aFrame);
+
+ // Create a continuing area frame
+ nsIFrame* blockFrame = aFrame->PrincipalChildList().FirstChild();
+ nsIFrame* continuingBlockFrame =
+ CreateContinuingFrame(blockFrame, cellFrame);
+
+ SetInitialSingleChild(cellFrame, continuingBlockFrame);
+ newFrame = cellFrame;
+ } else if (LayoutFrameType::Line == frameType) {
+ newFrame = NS_NewFirstLineFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Letter == frameType) {
+ newFrame = NS_NewFirstLetterFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Image == frameType) {
+ auto* imageFrame = static_cast<nsImageFrame*>(aFrame);
+ newFrame = imageFrame->CreateContinuingFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::ImageControl == frameType) {
+ newFrame = NS_NewImageControlFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::FieldSet == frameType) {
+ newFrame = NS_NewFieldSetFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::FlexContainer == frameType) {
+ newFrame = NS_NewFlexContainerFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::GridContainer == frameType) {
+ newFrame = NS_NewGridContainerFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::Ruby == frameType) {
+ newFrame = NS_NewRubyFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::RubyBaseContainer == frameType) {
+ newFrame = NS_NewRubyBaseContainerFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else if (LayoutFrameType::RubyTextContainer == frameType) {
+ newFrame = NS_NewRubyTextContainerFrame(mPresShell, computedStyle);
+ newFrame->Init(content, aParentFrame, aFrame);
+ } else {
+ MOZ_CRASH("unexpected frame type");
+ }
+
+ // Init() set newFrame to be a fluid continuation of aFrame.
+ // If we want a non-fluid continuation, we need to call SetPrevContinuation()
+ // to reset NS_FRAME_IS_FLUID_CONTINUATION.
+ if (!aIsFluid) {
+ newFrame->SetPrevContinuation(aFrame);
+ }
+
+ // If a continuing frame needs to carry frame state bits from its previous
+ // continuation or parent, set them in nsIFrame::Init(), or in any derived
+ // frame class's Init() if the bits are belong to specific group.
+
+ if (nextInFlow) {
+ nextInFlow->SetPrevInFlow(newFrame);
+ newFrame->SetNextInFlow(nextInFlow);
+ } else if (nextContinuation) {
+ nextContinuation->SetPrevContinuation(newFrame);
+ newFrame->SetNextContinuation(nextContinuation);
+ }
+
+ // aFrame cannot be a dynamic reflow root because it has a continuation now.
+ aFrame->RemoveStateBits(NS_FRAME_DYNAMIC_REFLOW_ROOT);
+
+ MOZ_ASSERT(!newFrame->GetNextSibling(), "unexpected sibling");
+ return newFrame;
+}
+
+nsresult nsCSSFrameConstructor::ReplicateFixedFrames(
+ nsPageContentFrame* aParentFrame) {
+ // Now deal with fixed-pos things.... They should appear on all pages,
+ // so we want to move over the placeholders when processing the child
+ // of the pageContentFrame.
+
+ nsIFrame* prevPageContentFrame = aParentFrame->GetPrevInFlow();
+ if (!prevPageContentFrame) {
+ return NS_OK;
+ }
+ nsContainerFrame* canvasFrame =
+ do_QueryFrame(aParentFrame->PrincipalChildList().FirstChild());
+ nsIFrame* prevCanvasFrame =
+ prevPageContentFrame->PrincipalChildList().FirstChild();
+ if (!canvasFrame || !prevCanvasFrame) {
+ // document's root element frame missing
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ nsFrameList fixedPlaceholders;
+ nsIFrame* firstFixed =
+ prevPageContentFrame->GetChildList(FrameChildListID::Fixed).FirstChild();
+ if (!firstFixed) {
+ return NS_OK;
+ }
+
+ // Don't allow abs-pos descendants of the fixed content to escape the content.
+ // This should not normally be possible (because fixed-pos elements should
+ // be absolute containers) but fixed-pos tables currently aren't abs-pos
+ // containers.
+ nsFrameConstructorState state(mPresShell, aParentFrame, nullptr,
+ mRootElementFrame);
+ state.mCreatingExtraFrames = true;
+
+ // We can't use an ancestor filter here, because we're not going to
+ // be usefully recurring down the tree. This means that other
+ // places in frame construction can't assume a filter is
+ // initialized!
+
+ // Iterate across fixed frames and replicate each whose placeholder is a
+ // descendant of aFrame. (We don't want to explicitly copy placeholders that
+ // are within fixed frames, because that would cause duplicates on the new
+ // page - bug 389619)
+ for (nsIFrame* fixed = firstFixed; fixed; fixed = fixed->GetNextSibling()) {
+ nsIFrame* prevPlaceholder = fixed->GetPlaceholderFrame();
+ if (prevPlaceholder && nsLayoutUtils::IsProperAncestorFrame(
+ prevCanvasFrame, prevPlaceholder)) {
+ // We want to use the same style as the primary style frame for
+ // our content
+ nsIContent* content = fixed->GetContent();
+ ComputedStyle* computedStyle =
+ nsLayoutUtils::GetStyleFrame(content)->Style();
+ AutoFrameConstructionItemList items(this);
+ AddFrameConstructionItemsInternal(state, content, canvasFrame, true,
+ computedStyle,
+ {ItemFlag::AllowPageBreak}, items);
+ ConstructFramesFromItemList(state, items, canvasFrame,
+ /* aParentIsWrapperAnonBox = */ false,
+ fixedPlaceholders);
+ }
+ }
+
+ // Add the placeholders to our primary child list.
+ // XXXbz this is a little screwed up, since the fixed frames will have
+ // broken auto-positioning. Oh, well.
+ NS_ASSERTION(!canvasFrame->PrincipalChildList().FirstChild(),
+ "leaking frames; doc root continuation must be empty");
+ canvasFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(fixedPlaceholders));
+ return NS_OK;
+}
+
+nsCSSFrameConstructor::InsertionPoint nsCSSFrameConstructor::GetInsertionPoint(
+ nsIContent* aChild) {
+ MOZ_ASSERT(aChild);
+ nsIContent* insertionElement = aChild->GetFlattenedTreeParent();
+ if (!insertionElement) {
+ // The element doesn't belong in the flattened tree, and thus we don't want
+ // to render it.
+ return {};
+ }
+
+ return {GetContentInsertionFrameFor(insertionElement), insertionElement};
+}
+
+// Capture state for the frame tree rooted at the frame associated with the
+// content object, aContent
+void nsCSSFrameConstructor::CaptureStateForFramesOf(
+ nsIContent* aContent, nsILayoutHistoryState* aHistoryState) {
+ if (!aHistoryState) {
+ return;
+ }
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame == mRootElementFrame) {
+ frame = mRootElementFrame
+ ? GetAbsoluteContainingBlock(mRootElementFrame, FIXED_POS)
+ : GetRootFrame();
+ }
+ for (; frame;
+ frame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(frame)) {
+ CaptureFrameState(frame, aHistoryState);
+ }
+}
+
+static bool IsWhitespaceFrame(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "invalid argument");
+ return aFrame->IsTextFrame() && aFrame->GetContent()->TextIsOnlyWhitespace();
+}
+
+static nsIFrame* FindFirstNonWhitespaceChild(nsIFrame* aParentFrame) {
+ nsIFrame* f = aParentFrame->PrincipalChildList().FirstChild();
+ while (f && IsWhitespaceFrame(f)) {
+ f = f->GetNextSibling();
+ }
+ return f;
+}
+
+static nsIFrame* FindNextNonWhitespaceSibling(nsIFrame* aFrame) {
+ nsIFrame* f = aFrame;
+ do {
+ f = f->GetNextSibling();
+ } while (f && IsWhitespaceFrame(f));
+ return f;
+}
+
+static nsIFrame* FindPreviousNonWhitespaceSibling(nsIFrame* aFrame) {
+ nsIFrame* f = aFrame;
+ do {
+ f = f->GetPrevSibling();
+ } while (f && IsWhitespaceFrame(f));
+ return f;
+}
+
+bool nsCSSFrameConstructor::MaybeRecreateContainerForFrameRemoval(
+ nsIFrame* aFrame) {
+#define TRACE(reason) \
+ PROFILER_MARKER("MaybeRecreateContainerForFrameRemoval: " reason, LAYOUT, \
+ {}, Tracing, "Layout")
+ MOZ_ASSERT(aFrame, "Must have a frame");
+ MOZ_ASSERT(aFrame->GetParent(), "Frame shouldn't be root");
+ MOZ_ASSERT(aFrame == aFrame->FirstContinuation(),
+ "aFrame not the result of GetPrimaryFrame()?");
+
+ nsIFrame* inFlowFrame = aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)
+ ? aFrame->GetPlaceholderFrame()
+ : aFrame;
+ MOZ_ASSERT(inFlowFrame, "How did that happen?");
+ MOZ_ASSERT(inFlowFrame == inFlowFrame->FirstContinuation(),
+ "placeholder for primary frame has previous continuations?");
+ nsIFrame* parent = inFlowFrame->GetParent();
+
+ if (inFlowFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ nsIFrame* grandparent = parent->GetParent();
+ MOZ_ASSERT(grandparent);
+
+ bool needsReframe =
+ // 1. Removing a column-span may lead to an empty
+ // ::-moz-column-span-wrapper.
+ inFlowFrame->IsColumnSpan() ||
+ // 2. Removing a frame which has any column-span siblings may also
+ // lead to an empty ::-moz-column-span-wrapper subtree. The
+ // column-span siblings were the frame's children, but later become
+ // the frame's siblings after CreateColumnSpanSiblings().
+ inFlowFrame->HasColumnSpanSiblings() ||
+ // 3. Removing the only child of a ::-moz-column-content, whose
+ // ColumnSet grandparent has a previous column-span sibling, requires
+ // reframing since we might connect the ColumnSet's next column-span
+ // sibling (if there's one). Note that this isn't actually needed if
+ // the ColumnSet is at the end of ColumnSetWrapper since we create
+ // empty ones at the end anyway, but we're not worried about
+ // optimizing that case.
+ (parent->Style()->GetPseudoType() == PseudoStyleType::columnContent &&
+ // The only child in ::-moz-column-content (might be tall enough to
+ // split across columns)
+ !inFlowFrame->GetPrevSibling() && !inFlowFrame->GetNextSibling() &&
+ // That ::-moz-column-content is the first column.
+ !parent->GetPrevInFlow() &&
+ // The ColumnSet grandparent has a previous sibling that is a
+ // column-span.
+ grandparent->GetPrevSibling());
+
+ if (needsReframe) {
+ nsContainerFrame* containingBlock =
+ GetMultiColumnContainingBlockFor(inFlowFrame);
+
+#ifdef DEBUG
+ if (IsFramePartOfIBSplit(inFlowFrame)) {
+ nsIFrame* ibContainingBlock = GetIBContainingBlockFor(inFlowFrame);
+ MOZ_ASSERT(containingBlock == ibContainingBlock ||
+ nsLayoutUtils::IsProperAncestorFrame(containingBlock,
+ ibContainingBlock),
+ "Multi-column containing block should be equal to or be the "
+ "ancestor of the IB containing block!");
+ }
+#endif
+
+ TRACE("Multi-column");
+ RecreateFramesForContent(containingBlock->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+ }
+
+ if (IsFramePartOfIBSplit(aFrame)) {
+ // The removal functions can't handle removal of an {ib} split directly; we
+ // need to rebuild the containing block.
+ TRACE("IB split removal");
+ ReframeContainingBlock(aFrame);
+ return true;
+ }
+
+ if (inFlowFrame->IsRenderedLegend()) {
+ TRACE("Fieldset / Legend");
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // Now check for possibly needing to reconstruct due to a pseudo parent
+ // For the case of ruby pseudo parent, effectively, only pseudo rb/rt frame
+ // need to be checked here, since all other types of parent will be catched
+ // by "Check ruby containers" section below.
+ if (IsTableOrRubyPseudo(parent)) {
+ if (FindFirstNonWhitespaceChild(parent) == inFlowFrame ||
+ !FindNextNonWhitespaceSibling(inFlowFrame->LastContinuation()) ||
+ // If it is a whitespace, and is the only child of the parent, the
+ // pseudo parent was created for the space, and should now be removed.
+ (IsWhitespaceFrame(aFrame) &&
+ parent->PrincipalChildList().OnlyChild()) ||
+ // If we're a table-column-group, then the OnlyChild check above is
+ // not going to catch cases when we're the first child.
+ (inFlowFrame->IsTableColGroupFrame() &&
+ parent->GetChildList(FrameChildListID::ColGroup).FirstChild() ==
+ inFlowFrame) ||
+ // Similar if we're a table-caption.
+ (inFlowFrame->IsTableCaption() &&
+ parent->GetChildList(FrameChildListID::Caption).FirstChild() ==
+ inFlowFrame)) {
+ TRACE("Table or ruby pseudo parent");
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+ }
+
+ // Might need to reconstruct things if this frame's nextSibling is a table
+ // or ruby pseudo, since removal of this frame might mean that this pseudo
+ // needs to get merged with the frame's prevSibling if that's also a table
+ // or ruby pseudo.
+ nsIFrame* nextSibling =
+ FindNextNonWhitespaceSibling(inFlowFrame->LastContinuation());
+ NS_ASSERTION(!IsTableOrRubyPseudo(inFlowFrame), "Shouldn't happen here");
+ // Effectively, for the ruby pseudo sibling case, only pseudo <ruby> frame
+ // need to be checked here, since all other types of such frames will have
+ // a ruby container parent, and be catched by "Check ruby containers" below.
+ if (nextSibling && IsTableOrRubyPseudo(nextSibling)) {
+ nsIFrame* prevSibling = FindPreviousNonWhitespaceSibling(inFlowFrame);
+ if (prevSibling && IsTableOrRubyPseudo(prevSibling)) {
+ TRACE("Table or ruby pseudo sibling");
+ // Good enough to recreate frames for aFrame's parent's content; even if
+ // aFrame's parent is a pseudo, that'll be the right content node.
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+ }
+
+ // Check ruby containers
+ LayoutFrameType parentType = parent->Type();
+ if (parentType == LayoutFrameType::Ruby ||
+ RubyUtils::IsRubyContainerBox(parentType)) {
+ // In ruby containers, pseudo frames may be created from
+ // whitespaces or even nothing. There are two cases we actually
+ // need to handle here, but hard to check exactly:
+ // 1. Status of spaces beside the frame may vary, and related
+ // frames may be constructed or destroyed accordingly.
+ // 2. The type of the first child of a ruby frame determines
+ // whether a pseudo ruby base container should exist.
+ TRACE("Ruby container");
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // Might need to reconstruct things if the removed frame's nextSibling is an
+ // anonymous flex item. The removed frame might've been what divided two
+ // runs of inline content into two anonymous flex items, which would now
+ // need to be merged.
+ // NOTE: It's fine that we've advanced nextSibling past whitespace (up above);
+ // we're only interested in anonymous flex items here, and those can never
+ // be adjacent to whitespace, since they absorb contiguous runs of inline
+ // non-replaced content (including whitespace).
+ if (nextSibling && IsAnonymousItem(nextSibling)) {
+ AssertAnonymousFlexOrGridItemParent(nextSibling, parent);
+ TRACE("Anon flex or grid item next sibling");
+ // Recreate frames for the flex container (the removed frame's parent)
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // Might need to reconstruct things if the removed frame's nextSibling is
+ // null and its parent is an anonymous flex item. (This might be the last
+ // remaining child of that anonymous flex item, which can then go away.)
+ if (!nextSibling && IsAnonymousItem(parent)) {
+ AssertAnonymousFlexOrGridItemParent(parent, parent->GetParent());
+ TRACE("Anon flex or grid item parent");
+ // Recreate frames for the flex container (the removed frame's grandparent)
+ RecreateFramesForContent(parent->GetParent()->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+
+ // Reconstruct if inflowFrame is parent's only child, and parent is, or has,
+ // a non-fluid continuation, i.e. it was split by bidi resolution
+ if (!inFlowFrame->GetPrevSibling() && !inFlowFrame->GetNextSibling() &&
+ ((parent->GetPrevContinuation() && !parent->GetPrevInFlow()) ||
+ (parent->GetNextContinuation() && !parent->GetNextInFlow()))) {
+ TRACE("Removing last child of non-fluid split parent");
+ RecreateFramesForContent(parent->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // We might still need to reconstruct things if the parent of inFlowFrame is
+ // ib-split, since in that case the removal of aFrame might affect the
+ // splitting of its parent.
+ if (!IsFramePartOfIBSplit(parent)) {
+ return false;
+ }
+
+ // If inFlowFrame is not the only in-flow child of |parent|, then removing
+ // it will change nothing about the {ib} split.
+ if (inFlowFrame != parent->PrincipalChildList().FirstChild() ||
+ inFlowFrame->LastContinuation()->GetNextSibling()) {
+ return false;
+ }
+
+ // If the parent is the first or last part of the {ib} split, then
+ // removing one of its kids will have no effect on the splitting.
+ // Get the first continuation up front so we don't have to do it twice.
+ nsIFrame* parentFirstContinuation = parent->FirstContinuation();
+ if (!GetIBSplitSibling(parentFirstContinuation) ||
+ !GetIBSplitPrevSibling(parentFirstContinuation)) {
+ return false;
+ }
+
+ TRACE("IB split parent");
+ ReframeContainingBlock(parent);
+ return true;
+#undef TRACE
+}
+
+void nsCSSFrameConstructor::UpdateTableCellSpans(nsIContent* aContent) {
+ nsTableCellFrame* cellFrame = do_QueryFrame(aContent->GetPrimaryFrame());
+
+ // It's possible that this warning could fire if some other style change
+ // simultaneously changes the 'display' of the element and makes it no
+ // longer be a table cell.
+ NS_WARNING_ASSERTION(cellFrame, "Hint should only be posted on table cells!");
+
+ if (cellFrame) {
+ cellFrame->GetTableFrame()->RowOrColSpanChanged(cellFrame);
+ }
+}
+
+static nsIContent* GetTopmostMathMLElement(nsIContent* aMathMLContent) {
+ MOZ_ASSERT(aMathMLContent->IsMathMLElement());
+ MOZ_ASSERT(aMathMLContent->GetPrimaryFrame());
+ MOZ_ASSERT(aMathMLContent->GetPrimaryFrame()->IsMathMLFrame());
+ nsIContent* root = aMathMLContent;
+
+ for (nsIContent* parent = aMathMLContent->GetFlattenedTreeParent(); parent;
+ parent = parent->GetFlattenedTreeParent()) {
+ nsIFrame* frame = parent->GetPrimaryFrame();
+ if (!frame || !frame->IsMathMLFrame()) {
+ break;
+ }
+ root = parent;
+ }
+
+ return root;
+}
+
+// We don't know how to re-insert an anonymous subtree root, so recreate the
+// closest non-generated ancestor instead, except for a few special cases...
+static bool ShouldRecreateContainerForNativeAnonymousContentRoot(
+ nsIContent* aContent) {
+ if (!aContent->IsRootOfNativeAnonymousSubtree()) {
+ return false;
+ }
+ if (ManualNACPtr::IsManualNAC(aContent)) {
+ // Editor NAC, would enter an infinite loop, and we sorta get away with it
+ // because it's all abspos.
+ return false;
+ }
+ if (auto* el = Element::FromNode(aContent)) {
+ if (auto* classes = el->GetClasses()) {
+ if (classes->Contains(nsGkAtoms::mozCustomContentContainer,
+ eCaseMatters)) {
+ // Canvas anonymous content (like the custom content container) is also
+ // fine, because its only sibling is a tooltip which is also abspos, so
+ // relative insertion order doesn't really matter.
+ //
+ // This is important because the inspector uses it, and we don't want
+ // inspecting the page to change behavior heavily (and reframing
+ // unfortunately has side-effects sometimes, even though they're bugs).
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+void nsCSSFrameConstructor::RecreateFramesForContent(
+ nsIContent* aContent, InsertionKind aInsertionKind) {
+ MOZ_ASSERT(aContent);
+
+ // If there is no document, we don't want to recreate frames for it. (You
+ // shouldn't generally be giving this method content without a document
+ // anyway).
+ // Rebuilding the frame tree can have bad effects, especially if it's the
+ // frame tree for chrome (see bug 157322).
+ if (NS_WARN_IF(!aContent->GetComposedDoc())) {
+ return;
+ }
+
+ // TODO(emilio): We technically can find the right insertion point nowadays
+ // using StyleChildrenIterator rather than FlattenedTreeIterator. But we'd
+ // need to tweak the setup to insert into replaced elements to filter which
+ // anonymous roots can be allowed, and which can't.
+ //
+ // TODO(emilio, 2022): Is this true? If we have a replaced element we wouldn't
+ // have generated e.g., a ::before/::after pseudo-element to begin with (which
+ // is what this code is about, so maybe we can just remove this piece of code
+ // altogether).
+ if (ShouldRecreateContainerForNativeAnonymousContentRoot(aContent)) {
+ do {
+ aContent = aContent->GetParent();
+ } while (ShouldRecreateContainerForNativeAnonymousContentRoot(aContent));
+ return RecreateFramesForContent(aContent, InsertionKind::Async);
+ }
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (frame && frame->IsMathMLFrame()) {
+ // Reframe the topmost MathML element to prevent exponential blowup
+ // (see bug 397518).
+ aContent = GetTopmostMathMLElement(aContent);
+ frame = aContent->GetPrimaryFrame();
+ }
+
+ if (frame) {
+ nsIFrame* parent = frame->GetParent();
+ nsIContent* parentContent = parent ? parent->GetContent() : nullptr;
+ // If the parent frame is a leaf then the subsequent insert will fail to
+ // create a frame, so we need to recreate the parent content. This happens
+ // with native anonymous content from the editor.
+ if (parent && parent->IsLeaf() && parentContent &&
+ parentContent != aContent) {
+ return RecreateFramesForContent(parentContent, InsertionKind::Async);
+ }
+ }
+
+ if (frame && MaybeRecreateContainerForFrameRemoval(frame)) {
+ return;
+ }
+
+ MOZ_ASSERT(aContent->GetParentNode());
+
+ // Remove the frames associated with the content object.
+ nsIContent* nextSibling = aContent->IsRootOfNativeAnonymousSubtree()
+ ? nullptr
+ : aContent->GetNextSibling();
+ bool didReconstruct =
+ ContentRemoved(aContent, nextSibling, REMOVE_FOR_RECONSTRUCTION);
+
+ if (!didReconstruct) {
+ if (aInsertionKind == InsertionKind::Async && aContent->IsElement()) {
+ // FIXME(emilio, bug 1397239): There's nothing removing the frame state
+ // for elements that go away before we come back to the frame
+ // constructor.
+ //
+ // Also, it'd be nice to just use the `ContentRangeInserted` path for
+ // both elements and non-elements, but we need to make lazy frame
+ // construction to apply to all elements first.
+ RestyleManager()->PostRestyleEvent(aContent->AsElement(), RestyleHint{0},
+ nsChangeHint_ReconstructFrame);
+ } else {
+ // Now, recreate the frames associated with this content object. If
+ // ContentRemoved triggered reconstruction, then we don't need to do this
+ // because the frames will already have been built.
+ ContentRangeInserted(aContent, aContent->GetNextSibling(),
+ aInsertionKind);
+ }
+ }
+}
+
+bool nsCSSFrameConstructor::DestroyFramesFor(nsIContent* aContent) {
+ MOZ_ASSERT(aContent && aContent->GetParentNode());
+
+ nsIContent* nextSibling = aContent->IsRootOfNativeAnonymousSubtree()
+ ? nullptr
+ : aContent->GetNextSibling();
+
+ return ContentRemoved(aContent, nextSibling, REMOVE_FOR_RECONSTRUCTION);
+}
+
+//////////////////////////////////////////////////////////////////////
+
+// Block frame construction code
+
+already_AddRefed<ComputedStyle> nsCSSFrameConstructor::GetFirstLetterStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle) {
+ if (aContent) {
+ return mPresShell->StyleSet()->ResolvePseudoElementStyle(
+ *aContent->AsElement(), PseudoStyleType::firstLetter, nullptr,
+ aComputedStyle);
+ }
+ return nullptr;
+}
+
+already_AddRefed<ComputedStyle> nsCSSFrameConstructor::GetFirstLineStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle) {
+ if (aContent) {
+ return mPresShell->StyleSet()->ResolvePseudoElementStyle(
+ *aContent->AsElement(), PseudoStyleType::firstLine, nullptr,
+ aComputedStyle);
+ }
+ return nullptr;
+}
+
+// Predicate to see if a given content (block element) has
+// first-letter style applied to it.
+bool nsCSSFrameConstructor::ShouldHaveFirstLetterStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle) {
+ return nsLayoutUtils::HasPseudoStyle(aContent, aComputedStyle,
+ PseudoStyleType::firstLetter,
+ mPresShell->GetPresContext());
+}
+
+bool nsCSSFrameConstructor::HasFirstLetterStyle(nsIFrame* aBlockFrame) {
+ MOZ_ASSERT(aBlockFrame, "Need a frame");
+ NS_ASSERTION(aBlockFrame->IsBlockFrameOrSubclass(), "Not a block frame?");
+
+ return aBlockFrame->HasAnyStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
+}
+
+bool nsCSSFrameConstructor::ShouldHaveFirstLineStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle) {
+ bool hasFirstLine = nsLayoutUtils::HasPseudoStyle(
+ aContent, aComputedStyle, PseudoStyleType::firstLine,
+ mPresShell->GetPresContext());
+ return hasFirstLine && !aContent->IsHTMLElement(nsGkAtoms::fieldset);
+}
+
+void nsCSSFrameConstructor::ShouldHaveSpecialBlockStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle,
+ bool* aHaveFirstLetterStyle, bool* aHaveFirstLineStyle) {
+ *aHaveFirstLetterStyle = ShouldHaveFirstLetterStyle(aContent, aComputedStyle);
+ *aHaveFirstLineStyle = ShouldHaveFirstLineStyle(aContent, aComputedStyle);
+}
+
+/* static */
+const nsCSSFrameConstructor::PseudoParentData
+ nsCSSFrameConstructor::sPseudoParentData[eParentTypeCount] = {
+ // Cell
+ {{&nsCSSFrameConstructor::ConstructTableCell,
+ FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRow)},
+ PseudoStyleType::tableCell},
+ // Row
+ {{&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRowGroup)},
+ PseudoStyleType::tableRow},
+ // Row group
+ {{&nsCSSFrameConstructor::ConstructTableRowOrRowGroup,
+ FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable)},
+ PseudoStyleType::tableRowGroup},
+ // Column group
+ {{ToCreationFunc(NS_NewTableColGroupFrame),
+ FCDATA_IS_TABLE_PART | FCDATA_SKIP_FRAMESET |
+ FCDATA_DISALLOW_OUT_OF_FLOW | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_SKIP_ABSPOS_PUSH |
+ // Not FCDATA_IS_WRAPPER_ANON_BOX, because we don't need to
+ // restyle these: they have non-inheriting styles.
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeTable)},
+ PseudoStyleType::tableColGroup},
+ // Table
+ {{&nsCSSFrameConstructor::ConstructTable,
+ FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX},
+ PseudoStyleType::table},
+ // Ruby
+ {{ToCreationFunc(NS_NewRubyFrame),
+ FCDATA_IS_LINE_PARTICIPANT | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX | FCDATA_SKIP_FRAMESET},
+ PseudoStyleType::ruby},
+ // Ruby Base
+ {{ToCreationFunc(NS_NewRubyBaseFrame),
+ FCDATA_USE_CHILD_ITEMS | FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyBaseContainer) |
+ FCDATA_SKIP_FRAMESET},
+ PseudoStyleType::rubyBase},
+ // Ruby Base Container
+ {{ToCreationFunc(NS_NewRubyBaseContainerFrame),
+ FCDATA_USE_CHILD_ITEMS | FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby) |
+ FCDATA_SKIP_FRAMESET},
+ PseudoStyleType::rubyBaseContainer},
+ // Ruby Text
+ {{ToCreationFunc(NS_NewRubyTextFrame),
+ FCDATA_USE_CHILD_ITEMS | FCDATA_IS_LINE_PARTICIPANT |
+ FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRubyTextContainer) |
+ FCDATA_SKIP_FRAMESET},
+ PseudoStyleType::rubyText},
+ // Ruby Text Container
+ {{ToCreationFunc(NS_NewRubyTextContainerFrame),
+ FCDATA_USE_CHILD_ITEMS | FCDATA_IS_WRAPPER_ANON_BOX |
+ FCDATA_DESIRED_PARENT_TYPE_TO_BITS(eTypeRuby) |
+ FCDATA_SKIP_FRAMESET},
+ PseudoStyleType::rubyTextContainer}};
+
+void nsCSSFrameConstructor::CreateNeededAnonFlexOrGridItems(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame) {
+ if (aItems.IsEmpty()) {
+ return;
+ }
+
+ if (!aParentFrame->IsFlexOrGridContainer()) {
+ return;
+ }
+
+ const bool isLegacyWebKitBox =
+ IsFlexContainerForLegacyWebKitBox(aParentFrame);
+ FCItemIterator iter(aItems);
+ do {
+ // Advance iter past children that don't want to be wrapped
+ if (iter.SkipItemsThatDontNeedAnonFlexOrGridItem(aState,
+ isLegacyWebKitBox)) {
+ // Hit the end of the items without finding any remaining children that
+ // need to be wrapped. We're finished!
+ return;
+ }
+
+ // If our next potentially-wrappable child is whitespace, then see if
+ // there's anything wrappable immediately after it. If not, we just drop
+ // the whitespace and move on. (We're not supposed to create any anonymous
+ // flex/grid items that _only_ contain whitespace).
+ // (BUT if this is generated content, then we don't give whitespace nodes
+ // any special treatment, because they're probably not really whitespace --
+ // they're just temporarily empty, waiting for their generated text.)
+ // XXXdholbert If this node's generated text will *actually end up being
+ // entirely whitespace*, then we technically should still skip over it, per
+ // the CSS grid & flexbox specs. I'm not bothering with that at this point,
+ // since it's a pretty extreme edge case.
+ if (!aParentFrame->IsGeneratedContentFrame() &&
+ iter.item().IsWhitespace(aState)) {
+ FCItemIterator afterWhitespaceIter(iter);
+ bool hitEnd = afterWhitespaceIter.SkipWhitespace(aState);
+ bool nextChildNeedsAnonItem =
+ !hitEnd && afterWhitespaceIter.item().NeedsAnonFlexOrGridItem(
+ aState, isLegacyWebKitBox);
+
+ if (!nextChildNeedsAnonItem) {
+ // There's nothing after the whitespace that we need to wrap, so we
+ // just drop this run of whitespace.
+ iter.DeleteItemsTo(this, afterWhitespaceIter);
+ if (hitEnd) {
+ // Nothing left to do -- we're finished!
+ return;
+ }
+ // else, we have a next child and it does not want to be wrapped. So,
+ // we jump back to the beginning of the loop to skip over that child
+ // (and anything else non-wrappable after it)
+ MOZ_ASSERT(!iter.IsDone() && !iter.item().NeedsAnonFlexOrGridItem(
+ aState, isLegacyWebKitBox),
+ "hitEnd and/or nextChildNeedsAnonItem lied");
+ continue;
+ }
+ }
+
+ // Now |iter| points to the first child that needs to be wrapped in an
+ // anonymous flex/grid item. Now we see how many children after it also want
+ // to be wrapped in an anonymous flex/grid item.
+ FCItemIterator endIter(iter); // iterator to find the end of the group
+ endIter.SkipItemsThatNeedAnonFlexOrGridItem(aState, isLegacyWebKitBox);
+
+ NS_ASSERTION(iter != endIter,
+ "Should've had at least one wrappable child to seek past");
+
+ // Now, we create the anonymous flex or grid item to contain the children
+ // between |iter| and |endIter|.
+ nsIContent* parentContent = aParentFrame->GetContent();
+ RefPtr<ComputedStyle> wrapperStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::anonymousItem, aParentFrame->Style());
+
+ static constexpr FrameConstructionData sBlockFormattingContextFCData(
+ ToCreationFunc(NS_NewBlockFormattingContext),
+ FCDATA_SKIP_FRAMESET | FCDATA_USE_CHILD_ITEMS |
+ FCDATA_IS_WRAPPER_ANON_BOX);
+
+ FrameConstructionItem* newItem = new (this)
+ FrameConstructionItem(&sBlockFormattingContextFCData,
+ // Use the content of our parent frame
+ parentContent, wrapperStyle.forget(), true);
+
+ newItem->mIsAllInline =
+ newItem->mComputedStyle->StyleDisplay()->IsInlineOutsideStyle();
+ newItem->mIsBlock = !newItem->mIsAllInline;
+
+ MOZ_ASSERT(!newItem->mIsAllInline && newItem->mIsBlock,
+ "expecting anonymous flex/grid items to be block-level "
+ "(this will make a difference when we encounter "
+ "'align-items: baseline')");
+
+ // Anonymous flex and grid items induce line boundaries around their
+ // contents.
+ newItem->mChildItems.SetLineBoundaryAtStart(true);
+ newItem->mChildItems.SetLineBoundaryAtEnd(true);
+ // The parent of the items in aItems is also the parent of the items
+ // in mChildItems
+ newItem->mChildItems.SetParentHasNoShadowDOM(aItems.ParentHasNoShadowDOM());
+
+ // Eat up all items between |iter| and |endIter| and put them in our
+ // wrapper. This advances |iter| to point to |endIter|.
+ iter.AppendItemsToList(this, endIter, newItem->mChildItems);
+
+ iter.InsertItem(newItem);
+ } while (!iter.IsDone());
+}
+
+/* static */ nsCSSFrameConstructor::RubyWhitespaceType
+nsCSSFrameConstructor::ComputeRubyWhitespaceType(StyleDisplay aPrevDisplay,
+ StyleDisplay aNextDisplay) {
+ MOZ_ASSERT(aPrevDisplay.IsRuby() && aNextDisplay.IsRuby());
+ if (aPrevDisplay == aNextDisplay &&
+ (aPrevDisplay == StyleDisplay::RubyBase ||
+ aPrevDisplay == StyleDisplay::RubyText)) {
+ return eRubyInterLeafWhitespace;
+ }
+ if (aNextDisplay == StyleDisplay::RubyText ||
+ aNextDisplay == StyleDisplay::RubyTextContainer) {
+ return eRubyInterLevelWhitespace;
+ }
+ return eRubyInterSegmentWhitespace;
+}
+
+/**
+ * This function checks the content from |aStartIter| to |aEndIter|,
+ * determines whether it contains only whitespace, and if yes,
+ * interprets the type of whitespace. This method does not change
+ * any of the iters.
+ */
+/* static */ nsCSSFrameConstructor::RubyWhitespaceType
+nsCSSFrameConstructor::InterpretRubyWhitespace(nsFrameConstructorState& aState,
+ const FCItemIterator& aStartIter,
+ const FCItemIterator& aEndIter) {
+ if (!aStartIter.item().IsWhitespace(aState)) {
+ return eRubyNotWhitespace;
+ }
+
+ FCItemIterator spaceEndIter(aStartIter);
+ spaceEndIter.SkipWhitespace(aState);
+ if (spaceEndIter != aEndIter) {
+ return eRubyNotWhitespace;
+ }
+
+ // Any leading or trailing whitespace in non-pseudo ruby box
+ // should have been trimmed, hence there should not be any
+ // whitespace at the start or the end.
+ MOZ_ASSERT(!aStartIter.AtStart() && !aEndIter.IsDone());
+ FCItemIterator prevIter(aStartIter);
+ prevIter.Prev();
+ return ComputeRubyWhitespaceType(
+ prevIter.item().mComputedStyle->StyleDisplay()->mDisplay,
+ aEndIter.item().mComputedStyle->StyleDisplay()->mDisplay);
+}
+
+/**
+ * This function eats up consecutive items which do not want the current
+ * parent into either a ruby base box or a ruby text box. When it
+ * returns, |aIter| points to the first item it doesn't wrap.
+ */
+void nsCSSFrameConstructor::WrapItemsInPseudoRubyLeafBox(
+ FCItemIterator& aIter, ComputedStyle* aParentStyle,
+ nsIContent* aParentContent) {
+ StyleDisplay parentDisplay = aParentStyle->StyleDisplay()->mDisplay;
+ ParentType parentType, wrapperType;
+ if (parentDisplay == StyleDisplay::RubyTextContainer) {
+ parentType = eTypeRubyTextContainer;
+ wrapperType = eTypeRubyText;
+ } else {
+ MOZ_ASSERT(parentDisplay == StyleDisplay::RubyBaseContainer);
+ parentType = eTypeRubyBaseContainer;
+ wrapperType = eTypeRubyBase;
+ }
+
+ MOZ_ASSERT(aIter.item().DesiredParentType() != parentType,
+ "Should point to something needs to be wrapped.");
+
+ FCItemIterator endIter(aIter);
+ endIter.SkipItemsNotWantingParentType(parentType);
+
+ WrapItemsInPseudoParent(aParentContent, aParentStyle, wrapperType, aIter,
+ endIter);
+}
+
+/**
+ * This function eats up consecutive items into a ruby level container.
+ * It may create zero or one level container. When it returns, |aIter|
+ * points to the first item it doesn't wrap.
+ */
+void nsCSSFrameConstructor::WrapItemsInPseudoRubyLevelContainer(
+ nsFrameConstructorState& aState, FCItemIterator& aIter,
+ ComputedStyle* aParentStyle, nsIContent* aParentContent) {
+ MOZ_ASSERT(aIter.item().DesiredParentType() != eTypeRuby,
+ "Pointing to a level container?");
+
+ FrameConstructionItem& firstItem = aIter.item();
+ ParentType wrapperType = firstItem.DesiredParentType();
+ if (wrapperType != eTypeRubyTextContainer) {
+ // If the first item is not ruby text,
+ // it should be in a base container.
+ wrapperType = eTypeRubyBaseContainer;
+ }
+
+ FCItemIterator endIter(aIter);
+ do {
+ if (endIter.SkipItemsWantingParentType(wrapperType) ||
+ // If the skipping above stops at some item which wants a
+ // different ruby parent, then we have finished.
+ IsRubyParentType(endIter.item().DesiredParentType())) {
+ // No more items need to be wrapped in this level container.
+ break;
+ }
+
+ FCItemIterator contentEndIter(endIter);
+ contentEndIter.SkipItemsNotWantingRubyParent();
+ // endIter must be on something doesn't want a ruby parent.
+ MOZ_ASSERT(contentEndIter != endIter);
+
+ // InterpretRubyWhitespace depends on the fact that any leading or
+ // trailing whitespace described in the spec have been trimmed at
+ // this point. With this precondition, it is safe not to check
+ // whether contentEndIter has been done.
+ RubyWhitespaceType whitespaceType =
+ InterpretRubyWhitespace(aState, endIter, contentEndIter);
+ if (whitespaceType == eRubyInterLevelWhitespace) {
+ // Remove inter-level whitespace.
+ bool atStart = (aIter == endIter);
+ endIter.DeleteItemsTo(this, contentEndIter);
+ if (atStart) {
+ aIter = endIter;
+ }
+ } else if (whitespaceType == eRubyInterSegmentWhitespace) {
+ // If this level container starts with inter-segment whitespaces,
+ // wrap them. Break at contentEndIter. Otherwise, leave it here.
+ // Break at endIter. They will be wrapped when we are here again.
+ if (aIter == endIter) {
+ MOZ_ASSERT(wrapperType == eTypeRubyBaseContainer,
+ "Inter-segment whitespace should be wrapped in rbc");
+ endIter = contentEndIter;
+ }
+ break;
+ } else if (wrapperType == eTypeRubyTextContainer &&
+ whitespaceType != eRubyInterLeafWhitespace) {
+ // Misparented inline content that's not inter-annotation
+ // whitespace doesn't belong in a pseudo ruby text container.
+ // Break at endIter.
+ break;
+ } else {
+ endIter = contentEndIter;
+ }
+ } while (!endIter.IsDone());
+
+ // It is possible that everything our parent wants us to wrap is
+ // simply an inter-level whitespace, which has been trimmed, or
+ // an inter-segment whitespace, which will be wrapped later.
+ // In those cases, don't create anything.
+ if (aIter != endIter) {
+ WrapItemsInPseudoParent(aParentContent, aParentStyle, wrapperType, aIter,
+ endIter);
+ }
+}
+
+/**
+ * This function trims leading and trailing whitespaces
+ * in the given item list.
+ */
+void nsCSSFrameConstructor::TrimLeadingAndTrailingWhitespaces(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems) {
+ FCItemIterator iter(aItems);
+ if (!iter.IsDone() && iter.item().IsWhitespace(aState)) {
+ FCItemIterator spaceEndIter(iter);
+ spaceEndIter.SkipWhitespace(aState);
+ iter.DeleteItemsTo(this, spaceEndIter);
+ }
+
+ iter.SetToEnd();
+ if (!iter.AtStart()) {
+ FCItemIterator spaceEndIter(iter);
+ do {
+ iter.Prev();
+ if (iter.AtStart()) {
+ // It's fine to not check the first item, because we
+ // should have trimmed leading whitespaces above.
+ break;
+ }
+ } while (iter.item().IsWhitespace(aState));
+ iter.Next();
+ if (iter != spaceEndIter) {
+ iter.DeleteItemsTo(this, spaceEndIter);
+ }
+ }
+}
+
+/**
+ * This function walks through the child list (aItems) and creates
+ * needed pseudo ruby boxes to wrap misparented children.
+ */
+void nsCSSFrameConstructor::CreateNeededPseudoInternalRubyBoxes(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame) {
+ const ParentType ourParentType = GetParentType(aParentFrame);
+ if (!IsRubyParentType(ourParentType) ||
+ aItems.AllWantParentType(ourParentType)) {
+ return;
+ }
+
+ if (!IsRubyPseudo(aParentFrame) ||
+ ourParentType == eTypeRuby /* for 'display:block ruby' */) {
+ // Normally, ruby pseudo frames start from and end at some elements,
+ // which means they don't have leading and trailing whitespaces at
+ // all. But there are two cases where they do actually have leading
+ // or trailing whitespaces:
+ // 1. It is an inter-segment whitespace which in an individual ruby
+ // base container.
+ // 2. The pseudo frame starts from or ends at consecutive inline
+ // content, which is not pure whitespace, but includes some.
+ // In either case, the whitespaces are not the leading or trailing
+ // whitespaces defined in the spec, and thus should not be trimmed.
+ TrimLeadingAndTrailingWhitespaces(aState, aItems);
+ }
+
+ FCItemIterator iter(aItems);
+ nsIContent* parentContent = aParentFrame->GetContent();
+ ComputedStyle* parentStyle = aParentFrame->Style();
+ while (!iter.IsDone()) {
+ if (!iter.SkipItemsWantingParentType(ourParentType)) {
+ if (ourParentType == eTypeRuby) {
+ WrapItemsInPseudoRubyLevelContainer(aState, iter, parentStyle,
+ parentContent);
+ } else {
+ WrapItemsInPseudoRubyLeafBox(iter, parentStyle, parentContent);
+ }
+ }
+ }
+}
+
+/*
+ * This function works as follows: we walk through the child list (aItems) and
+ * find items that cannot have aParentFrame as their parent. We wrap
+ * continuous runs of such items into a FrameConstructionItem for a frame that
+ * gets them closer to their desired parents. For example, a run of non-row
+ * children of a row-group will get wrapped in a row. When we later construct
+ * the frame for this wrapper (in this case for the row), it'll be the correct
+ * parent for the cells in the set of items we wrapped or we'll wrap cells
+ * around everything else. At the end of this method, aItems is guaranteed to
+ * contain only items for frames that can be direct kids of aParentFrame.
+ */
+void nsCSSFrameConstructor::CreateNeededPseudoContainers(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame) {
+ ParentType ourParentType = GetParentType(aParentFrame);
+ if (IsRubyParentType(ourParentType) ||
+ aItems.AllWantParentType(ourParentType)) {
+ // Nothing to do here
+ return;
+ }
+
+ FCItemIterator iter(aItems);
+ do {
+ if (iter.SkipItemsWantingParentType(ourParentType)) {
+ // Nothing else to do here; we're finished
+ return;
+ }
+
+ // Now we're pointing to the first child that wants a different parent
+ // type.
+
+ // Now try to figure out what kids we can group together. We can generally
+ // group everything that has a different desired parent type from us. Two
+ // exceptions to this:
+ // 1) If our parent type is table, we can't group columns with anything
+ // else other than whitespace.
+ // 2) Whitespace that lies between two things we can group which both want
+ // a non-block parent should be dropped, even if we can't group them
+ // with each other and even if the whitespace wants a parent of
+ // ourParentType. Ends of the list count as things that don't want a
+ // block parent (so that for example we'll drop a whitespace-only list).
+
+ FCItemIterator endIter(iter); /* iterator to find the end of the group */
+ ParentType groupingParentType = endIter.item().DesiredParentType();
+ if (aItems.AllWantParentType(groupingParentType) &&
+ groupingParentType != eTypeBlock) {
+ // Just group them all and be done with it. We need the check for
+ // eTypeBlock here to catch the "all the items are whitespace" case
+ // described above.
+ endIter.SetToEnd();
+ } else {
+ // Locate the end of the group.
+
+ // Keep track of the type the previous item wanted, in case we have to
+ // deal with whitespace. Start it off with ourParentType, since that's
+ // the last thing |iter| would have skipped over.
+ ParentType prevParentType = ourParentType;
+ do {
+ // Walk an iterator past any whitespace that we might be able to drop
+ // from the list
+ FCItemIterator spaceEndIter(endIter);
+ if (prevParentType != eTypeBlock &&
+ !aParentFrame->IsGeneratedContentFrame() &&
+ spaceEndIter.item().IsWhitespace(aState)) {
+ bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
+
+ // We drop the whitespace in the following cases:
+ // 1) If these are not trailing spaces and the next item wants a table
+ // or table-part parent
+ // 2) If these are trailing spaces and aParentFrame is a
+ // tabular container according to rule 1.3 of CSS 2.1 Sec 17.2.1.
+ // (Being a tabular container pretty much means ourParentType is
+ // not eTypeBlock besides the eTypeColGroup case, which won't
+ // reach here.)
+ if ((!trailingSpaces &&
+ IsTableParentType(spaceEndIter.item().DesiredParentType())) ||
+ (trailingSpaces && ourParentType != eTypeBlock)) {
+ bool updateStart = (iter == endIter);
+ endIter.DeleteItemsTo(this, spaceEndIter);
+ NS_ASSERTION(trailingSpaces == endIter.IsDone(),
+ "These should match");
+
+ if (updateStart) {
+ iter = endIter;
+ }
+
+ if (trailingSpaces) {
+ break; /* Found group end */
+ }
+
+ if (updateStart) {
+ // Update groupingParentType, since it might have been eTypeBlock
+ // just because of the whitespace.
+ groupingParentType = iter.item().DesiredParentType();
+ }
+ }
+ }
+
+ // Now endIter points to a non-whitespace item or a non-droppable
+ // whitespace item. In the latter case, if this is the end of the group
+ // we'll traverse this whitespace again. But it'll all just be quick
+ // DesiredParentType() checks which will match ourParentType (that's
+ // what it means that this is the group end), so it's OK.
+ // However, when we are grouping a ruby parent, and endIter points to
+ // a non-droppable whitespace, if the next non-whitespace item also
+ // wants a ruby parent, the whitespace should also be included into
+ // the current ruby container.
+ prevParentType = endIter.item().DesiredParentType();
+ if (prevParentType == ourParentType &&
+ (endIter == spaceEndIter || spaceEndIter.IsDone() ||
+ !IsRubyParentType(groupingParentType) ||
+ !IsRubyParentType(spaceEndIter.item().DesiredParentType()))) {
+ // End the group at endIter.
+ break;
+ }
+
+ if (ourParentType == eTypeTable &&
+ (prevParentType == eTypeColGroup) !=
+ (groupingParentType == eTypeColGroup)) {
+ // Either we started with columns and now found something else, or
+ // vice versa. In any case, end the grouping.
+ break;
+ }
+
+ // If we have some whitespace that we were not able to drop and there is
+ // an item after the whitespace that is already properly parented, then
+ // make sure to include the spaces in our group but stop the group after
+ // that.
+ if (spaceEndIter != endIter && !spaceEndIter.IsDone() &&
+ ourParentType == spaceEndIter.item().DesiredParentType()) {
+ endIter = spaceEndIter;
+ break;
+ }
+
+ // Include the whitespace we didn't drop (if any) in the group.
+ endIter = spaceEndIter;
+ prevParentType = endIter.item().DesiredParentType();
+
+ endIter.Next();
+ } while (!endIter.IsDone());
+ }
+
+ if (iter == endIter) {
+ // Nothing to wrap here; just skipped some whitespace
+ continue;
+ }
+
+ // Now group together all the items between iter and endIter. The right
+ // parent type to use depends on ourParentType.
+ ParentType wrapperType;
+ switch (ourParentType) {
+ case eTypeRow:
+ // The parent type for a cell is eTypeBlock, since that's what a cell
+ // looks like to its kids.
+ wrapperType = eTypeBlock;
+ break;
+ case eTypeRowGroup:
+ wrapperType = eTypeRow;
+ break;
+ case eTypeTable:
+ // Either colgroup or rowgroup, depending on what we're grouping.
+ wrapperType =
+ groupingParentType == eTypeColGroup ? eTypeColGroup : eTypeRowGroup;
+ break;
+ case eTypeColGroup:
+ MOZ_CRASH("Colgroups should be suppresing non-col child items");
+ default:
+ NS_ASSERTION(ourParentType == eTypeBlock, "Unrecognized parent type");
+ if (IsRubyParentType(groupingParentType)) {
+ wrapperType = eTypeRuby;
+ } else {
+ NS_ASSERTION(IsTableParentType(groupingParentType),
+ "groupingParentType should be either Ruby or table");
+ wrapperType = eTypeTable;
+ }
+ }
+
+ ComputedStyle* parentStyle = aParentFrame->Style();
+ WrapItemsInPseudoParent(aParentFrame->GetContent(), parentStyle,
+ wrapperType, iter, endIter);
+
+ // Now |iter| points to the item that was the first one we didn't wrap;
+ // loop and see whether we need to skip it or wrap it in something
+ // different.
+ } while (!iter.IsDone());
+}
+
+/**
+ * This method wraps frame construction item from |aIter| to
+ * |aEndIter|. After it returns, aIter points to the first item
+ * after the wrapper.
+ */
+void nsCSSFrameConstructor::WrapItemsInPseudoParent(
+ nsIContent* aParentContent, ComputedStyle* aParentStyle,
+ ParentType aWrapperType, FCItemIterator& aIter,
+ const FCItemIterator& aEndIter) {
+ const PseudoParentData& pseudoData = sPseudoParentData[aWrapperType];
+ PseudoStyleType pseudoType = pseudoData.mPseudoType;
+ auto& parentDisplay = *aParentStyle->StyleDisplay();
+ auto parentDisplayInside = parentDisplay.DisplayInside();
+
+ // XXXmats should we use IsInlineInsideStyle() here instead? seems odd to
+ // exclude RubyBaseContainer/RubyTextContainer...
+ if (pseudoType == PseudoStyleType::table &&
+ (parentDisplay.IsInlineFlow() ||
+ parentDisplayInside == StyleDisplayInside::RubyBase ||
+ parentDisplayInside == StyleDisplayInside::RubyText)) {
+ pseudoType = PseudoStyleType::inlineTable;
+ }
+
+ RefPtr<ComputedStyle> wrapperStyle;
+ if (pseudoData.mFCData.mBits & FCDATA_IS_WRAPPER_ANON_BOX) {
+ wrapperStyle = mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ pseudoType, aParentStyle);
+ } else {
+ wrapperStyle =
+ mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
+ pseudoType);
+ }
+
+ // Use the content of our parent frame
+ auto* newItem = new (this) FrameConstructionItem(
+ &pseudoData.mFCData, aParentContent, wrapperStyle.forget(), true);
+
+ const nsStyleDisplay* disp = newItem->mComputedStyle->StyleDisplay();
+ // Here we're cheating a tad... technically, table-internal items should be
+ // inline if aParentFrame is inline, but they'll get wrapped in an
+ // inline-table in the end, so it'll all work out. In any case, arguably
+ // we don't need to maintain this state at this point... but it's better
+ // to, I guess.
+ newItem->mIsAllInline = disp->IsInlineOutsideStyle();
+
+ bool isRuby = disp->IsRubyDisplayType();
+ if (!isRuby) {
+ // Table pseudo frames always induce line boundaries around their
+ // contents.
+ newItem->mChildItems.SetLineBoundaryAtStart(true);
+ newItem->mChildItems.SetLineBoundaryAtEnd(true);
+ }
+ // The parent of the items in aItems is also the parent of the items
+ // in mChildItems
+ newItem->mChildItems.SetParentHasNoShadowDOM(
+ aIter.List()->ParentHasNoShadowDOM());
+
+ // Eat up all items between |aIter| and |aEndIter| and put them in our
+ // wrapper Advances |aIter| to point to |aEndIter|.
+ aIter.AppendItemsToList(this, aEndIter, newItem->mChildItems);
+
+ aIter.InsertItem(newItem);
+}
+
+void nsCSSFrameConstructor::CreateNeededPseudoSiblings(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame) {
+ if (aItems.IsEmpty() || GetParentType(aParentFrame) != eTypeRuby) {
+ return;
+ }
+
+ FCItemIterator iter(aItems);
+ StyleDisplay firstDisplay =
+ iter.item().mComputedStyle->StyleDisplay()->mDisplay;
+ if (firstDisplay == StyleDisplay::RubyBaseContainer) {
+ return;
+ }
+ NS_ASSERTION(firstDisplay == StyleDisplay::RubyTextContainer,
+ "Child of ruby frame should either a rbc or a rtc");
+
+ const PseudoParentData& pseudoData =
+ sPseudoParentData[eTypeRubyBaseContainer];
+ RefPtr<ComputedStyle> pseudoStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ pseudoData.mPseudoType, aParentFrame->Style());
+ FrameConstructionItem* newItem = new (this) FrameConstructionItem(
+ &pseudoData.mFCData,
+ // Use the content of the parent frame
+ aParentFrame->GetContent(), pseudoStyle.forget(), true);
+ newItem->mIsAllInline = true;
+ newItem->mChildItems.SetParentHasNoShadowDOM(true);
+ iter.InsertItem(newItem);
+}
+
+#ifdef DEBUG
+/**
+ * Returns true iff aFrame should be wrapped in an anonymous flex/grid item,
+ * rather than being a direct child of aContainerFrame.
+ *
+ * NOTE: aContainerFrame must be a flex or grid container - this function is
+ * purely for sanity-checking the children of these container types.
+ * NOTE: See also NeedsAnonFlexOrGridItem(), for the non-debug version of this
+ * logic (which operates a bit earlier, on FCData instead of frames).
+ */
+static bool FrameWantsToBeInAnonymousItem(const nsIFrame* aContainerFrame,
+ const nsIFrame* aFrame) {
+ MOZ_ASSERT(aContainerFrame->IsFlexOrGridContainer());
+
+ // Any line-participant frames (e.g. text) definitely want to be wrapped in
+ // an anonymous flex/grid item.
+ if (aFrame->IsLineParticipant()) {
+ return true;
+ }
+
+ // If the container is a -webkit-{inline-}box container, then placeholders
+ // also need to be wrapped, for compatibility.
+ if (IsFlexContainerForLegacyWebKitBox(aContainerFrame) &&
+ aFrame->IsPlaceholderFrame()) {
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+static void VerifyGridFlexContainerChildren(nsIFrame* aParentFrame,
+ const nsFrameList& aChildren) {
+#ifdef DEBUG
+ if (!aParentFrame->IsFlexOrGridContainer()) {
+ return;
+ }
+
+ bool prevChildWasAnonItem = false;
+ for (const nsIFrame* child : aChildren) {
+ MOZ_ASSERT(!FrameWantsToBeInAnonymousItem(aParentFrame, child),
+ "frame wants to be inside an anonymous item, but it isn't");
+ if (IsAnonymousItem(child)) {
+ AssertAnonymousFlexOrGridItemParent(child, aParentFrame);
+ MOZ_ASSERT(!prevChildWasAnonItem, "two anon items in a row");
+ nsIFrame* firstWrappedChild = child->PrincipalChildList().FirstChild();
+ MOZ_ASSERT(firstWrappedChild, "anonymous item shouldn't be empty");
+ prevChildWasAnonItem = true;
+ } else {
+ prevChildWasAnonItem = false;
+ }
+ }
+#endif
+}
+
+static bool FrameHasOnlyPlaceholderPrevSiblings(const nsIFrame* aFrame) {
+ // Check for prev siblings, ignoring placeholder frames.
+ MOZ_ASSERT(aFrame, "frame must not be null");
+ const nsIFrame* prevSibling = aFrame;
+ do {
+ prevSibling = prevSibling->GetPrevSibling();
+ } while (prevSibling && prevSibling->IsPlaceholderFrame());
+ return !prevSibling;
+}
+
+static bool FrameHasOnlyPlaceholderNextSiblings(const nsIFrame* aFrame) {
+ // Check for next siblings, ignoring placeholder frames.
+ MOZ_ASSERT(aFrame, "frame must not be null");
+ const nsIFrame* nextSibling = aFrame;
+ do {
+ nextSibling = nextSibling->GetNextSibling();
+ } while (nextSibling && nextSibling->IsPlaceholderFrame());
+ return !nextSibling;
+}
+
+static void SetPageValues(nsIFrame* const aFrame,
+ const nsAtom* const aAutoValue,
+ const nsAtom* const aStartValue,
+ const nsAtom* const aEndValue) {
+ MOZ_ASSERT(aAutoValue, "Auto page value should never be null");
+ MOZ_ASSERT(aStartValue || aEndValue, "Should not have called with no values");
+ nsIFrame::PageValues* pageValues =
+ aFrame->GetProperty(nsIFrame::PageValuesProperty());
+
+ if (aStartValue) {
+ if (aStartValue == aAutoValue) {
+ // If the page value struct already exists, set the start value to null
+ // to indicate the auto value.
+ if (pageValues) {
+ pageValues->mStartPageValue = nullptr;
+ }
+ } else {
+ // The start value is not auto, so we need to store it, creating the
+ // page values struct if it does not already exist.
+ if (!pageValues) {
+ pageValues = new nsIFrame::PageValues();
+ aFrame->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
+ }
+ pageValues->mStartPageValue = aStartValue;
+ }
+ }
+ if (aEndValue) {
+ if (aEndValue == aAutoValue) {
+ // If the page value struct already exists, set the end value to null
+ // to indicate the auto value.
+ if (pageValues) {
+ pageValues->mEndPageValue = nullptr;
+ }
+ } else {
+ // The end value is not auto, so we need to store it, creating the
+ // page values struct if it does not already exist.
+ if (!pageValues) {
+ pageValues = new nsIFrame::PageValues();
+ aFrame->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
+ }
+ pageValues->mEndPageValue = aEndValue;
+ }
+ }
+}
+
+inline void nsCSSFrameConstructor::ConstructFramesFromItemList(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsContainerFrame* aParentFrame, bool aParentIsWrapperAnonBox,
+ nsFrameList& aFrameList) {
+#ifdef DEBUG
+ if (aParentFrame->StyleContent()->mContent.IsNone() &&
+ StaticPrefs::layout_css_element_content_none_enabled()) {
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ MOZ_ASSERT(iter.item().mContent->IsInNativeAnonymousSubtree() ||
+ iter.item().mComputedStyle->IsPseudoOrAnonBox());
+ }
+ }
+
+ // The assertion condition should match the logic in
+ // MaybePushFloatContainingBlock().
+ MOZ_ASSERT(!(ShouldSuppressFloatingOfDescendants(aParentFrame) ||
+ aParentFrame->IsFloatContainingBlock()) ||
+ aState.mFloatCBCandidate == aParentFrame,
+ "Our caller or ProcessChildren()'s caller should call "
+ "MaybePushFloatContainingBlock() to handle the float containing "
+ "block candidate!");
+ aState.mFloatCBCandidate = nullptr;
+#endif
+
+ // Ensure aParentIsWrapperAnonBox is correct. We _could_ compute it directly,
+ // but it would be a bit slow, which is why we pass it from callers, who have
+ // that information offhand in many cases.
+ MOZ_ASSERT(ParentIsWrapperAnonBox(aParentFrame) == aParentIsWrapperAnonBox);
+
+ // Note: we explicitly exclude TableColGroupFrame because it doesn't
+ // have the FCDATA_IS_WRAPPER_ANON_BOX on pseudos so aParentIsWrapperAnonBox
+ // is false for such pseudos (see sPseudoParentData below).
+ if (!aParentIsWrapperAnonBox && aState.mHasRenderedLegend &&
+ aParentFrame->GetContent()->IsHTMLElement(nsGkAtoms::fieldset) &&
+ !aParentFrame->IsTableColGroupFrame()) {
+ DebugOnly<bool> found = false;
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ if (iter.item().mIsRenderedLegend) {
+ // This makes the rendered legend the first frame in the fieldset child
+ // list which makes keyboard traversal follow the visual order.
+ nsFieldSetFrame* fieldSetFrame = GetFieldSetFrameFor(aParentFrame);
+ nsFrameList renderedLegend;
+ ConstructFramesFromItem(aState, iter, fieldSetFrame, renderedLegend);
+ MOZ_ASSERT(renderedLegend.OnlyChild(),
+ "a rendered legend should have exactly one frame");
+ fieldSetFrame->InsertFrames(FrameChildListID::Principal, nullptr,
+ nullptr, std::move(renderedLegend));
+ FCItemIterator next = iter;
+ next.Next();
+ iter.DeleteItemsTo(this, next);
+ found = true;
+ break;
+ }
+ }
+ MOZ_ASSERT(found, "should have found our rendered legend");
+ }
+
+ CreateNeededPseudoContainers(aState, aItems, aParentFrame);
+ CreateNeededAnonFlexOrGridItems(aState, aItems, aParentFrame);
+ CreateNeededPseudoInternalRubyBoxes(aState, aItems, aParentFrame);
+ CreateNeededPseudoSiblings(aState, aItems, aParentFrame);
+
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ MOZ_ASSERT(!iter.item().mIsRenderedLegend,
+ "Only one item can be the rendered legend, "
+ "and it should've been handled above");
+ NS_ASSERTION(iter.item().DesiredParentType() == GetParentType(aParentFrame),
+ "Needed pseudos didn't get created; expect bad things");
+ ConstructFramesFromItem(aState, iter, aParentFrame, aFrameList);
+ }
+
+ VerifyGridFlexContainerChildren(aParentFrame, aFrameList);
+
+ // Calculate and propagate page-name values for each frame in the frame list.
+ // We do not want to compute and propagate page-name values from frames that
+ // are children of any subclasses of block frames, but not actually a block
+ // frame. The page-name property does not apply to frames which cannot create
+ // class A breakpoints (currently no subclass of BlockFrame can). Because the
+ // property does not apply, those children also cannot propagate page-name
+ // values.
+ // This assumption helps avoid unnecessarily handling page-names for frames
+ // such as form controls, which also avoids bug 1819468.
+ if (aState.mPresContext->IsPaginated() && aParentFrame->IsBlockFrame()) {
+ // Set the start/end page values while iterating the frame list, to walk
+ // up the frame tree only once after iterating the frame list.
+ // This also avoids extra property lookups on these frames.
+ MOZ_ASSERT(aState.mAutoPageNameValue == aParentFrame->GetAutoPageValue(),
+ "aState.mAutoPageNameValue should have been equivalent to "
+ "the auto value stored on our parent frame.");
+ // Even though we store null for page values that equal the "auto" resolved
+ // value on frames, we always want startPageValue/endPageValue to be the
+ // actual atoms reflecting the start/end values. This is because when we
+ // propagate the values up the frame tree, we will need to compare them to
+ // the auto value for each ancestor. This value might be different than the
+ // auto value for this frame.
+ const nsAtom* startPageValue = nullptr;
+ const nsAtom* endPageValue = nullptr;
+ for (nsIFrame* f : aFrameList) {
+ if (f->IsPlaceholderFrame()) {
+ continue;
+ }
+ // Resolve auto against the parent frame's used page name, which has been
+ // determined and set on aState.mAutoPageNameValue. If this item is not
+ // block-level then we use the value that auto resolves to.
+ //
+ // This is to achieve the propagation behavior described in the spec:
+ //
+ // "A start page value and end page value is determined for each box as
+ // the value (if any) propagated from its first or last child box
+ // (respectively), else the used value on the box itself."
+ //
+ // "A child propagates its own start or end page value if and only if the
+ // page property applies to it."
+ //
+ // The page property only applies to "boxes that create class A break
+ // points". When taken together, this means that non block-level children
+ // do not propagate start/end page values, and instead we use "the used
+ // value on the box itself", the "box itself" being aParentFrame. This
+ // value has been determined and saved as aState.mAutoPageNameValue
+ //
+ // https://www.w3.org/TR/css-page-3/#using-named-pages
+ // https://www.w3.org/TR/css-break-3/#btw-blocks
+ const StylePageName& pageName = f->StylePage()->mPage;
+ const nsAtom* const pageNameAtom =
+ (pageName.IsPageName() && f->IsBlockOutside())
+ ? pageName.AsPageName().AsAtom()
+ : aState.mAutoPageNameValue;
+ nsIFrame::PageValues* pageValues =
+ f->GetProperty(nsIFrame::PageValuesProperty());
+ // If this frame has any children, it will already have had its page
+ // values set at this point. However, if no page values have been set,
+ // we must ensure that the appropriate PageValuesProperty value has been
+ // set.
+ // If the page name is equal to the auto value, then PageValuesProperty
+ // should remain null to indicate that the start/end values are both
+ // equal to the auto value.
+ if (pageNameAtom != aState.mAutoPageNameValue && !pageValues) {
+ pageValues = new nsIFrame::PageValues{pageNameAtom, pageNameAtom};
+ f->SetProperty(nsIFrame::PageValuesProperty(), pageValues);
+ }
+ // We don't want to use GetStartPageValue() or GetEndPageValue(), as each
+ // requires a property lookup which we can avoid here.
+ if (!startPageValue) {
+ startPageValue = (pageValues && pageValues->mStartPageValue)
+ ? pageValues->mStartPageValue.get()
+ : aState.mAutoPageNameValue;
+ }
+ endPageValue = (pageValues && pageValues->mEndPageValue)
+ ? pageValues->mEndPageValue.get()
+ : aState.mAutoPageNameValue;
+ MOZ_ASSERT(startPageValue && endPageValue,
+ "Should have found start/end page value");
+ }
+ MOZ_ASSERT(!startPageValue == !endPageValue,
+ "Should have set both or neither page values");
+ if (startPageValue) {
+ // Walk up the frame tree from our parent frame, propagating start and
+ // end page values.
+ // As we go, if we find that, for a frame, we are not contributing one of
+ // the start/end page values, then our subtree will not contribute this
+ // value from that frame onward. startPageValue/endPageValue are set to
+ // null to indicate this.
+ // Stop iterating when we are not contributing either start or end
+ // values, when we hit the root frame (no parent), or when we find a
+ // frame that is not a block frame.
+ for (nsContainerFrame* ancestorFrame = aParentFrame;
+ (startPageValue || endPageValue) && ancestorFrame &&
+ ancestorFrame->IsBlockFrame();
+ ancestorFrame = ancestorFrame->GetParent()) {
+ MOZ_ASSERT(!ancestorFrame->GetPrevInFlow(),
+ "Should not have fragmentation yet");
+ MOZ_ASSERT(ancestorFrame->mWasVisitedByAutoFrameConstructionPageName,
+ "Frame should have been visited by "
+ "AutoFrameConstructionPageName");
+ {
+ // Get what the auto value is, based on this frame's parent.
+ // For the root frame, `auto` resolves to the empty atom.
+ const nsContainerFrame* const parent = ancestorFrame->GetParent();
+ const nsAtom* const parentAuto = MOZ_LIKELY(parent)
+ ? parent->GetAutoPageValue()
+ : nsGkAtoms::_empty;
+ SetPageValues(ancestorFrame, parentAuto, startPageValue,
+ endPageValue);
+ }
+ // Once we stop contributing start/end values, we know there is a
+ // sibling subtree that contributed that value to our shared parent
+ // instead of our starting frame's subtree. This means once
+ // startPageValue/endPageValue becomes null, indicating that we are no
+ // longer contributing that page value, it should stay null and we no
+ // longer need to check for siblings in that direction.
+ if (startPageValue &&
+ !FrameHasOnlyPlaceholderPrevSiblings(ancestorFrame)) {
+ startPageValue = nullptr;
+ }
+ if (endPageValue &&
+ !FrameHasOnlyPlaceholderNextSiblings(ancestorFrame)) {
+ endPageValue = nullptr;
+ }
+ }
+ }
+ }
+
+ if (aParentIsWrapperAnonBox) {
+ for (nsIFrame* f : aFrameList) {
+ f->SetParentIsWrapperAnonBox();
+ }
+ }
+}
+
+void nsCSSFrameConstructor::AddFCItemsForAnonymousContent(
+ nsFrameConstructorState& aState, nsContainerFrame* aFrame,
+ const nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
+ FrameConstructionItemList& aItemsToConstruct,
+ const AutoFrameConstructionPageName&) {
+ for (const auto& info : aAnonymousItems) {
+ nsIContent* content = info.mContent;
+ // Gecko-styled nodes should have no pending restyle flags.
+ // Assert some things about this content
+ MOZ_ASSERT(!(content->GetFlags() &
+ (NODE_DESCENDANTS_NEED_FRAMES | NODE_NEEDS_FRAME)),
+ "Should not be marked as needing frames");
+ MOZ_ASSERT(!content->GetPrimaryFrame(), "Should have no existing frame");
+ MOZ_ASSERT(!content->IsComment() && !content->IsProcessingInstruction(),
+ "Why is someone creating garbage anonymous content");
+
+ // Make sure we eagerly performed the servo cascade when the anonymous
+ // nodes were created.
+ MOZ_ASSERT(!content->IsElement() || content->AsElement()->HasServoData());
+
+ RefPtr<ComputedStyle> computedStyle = ResolveComputedStyle(content);
+
+ AddFrameConstructionItemsInternal(aState, content, aFrame, true,
+ computedStyle, {ItemFlag::AllowPageBreak},
+ aItemsToConstruct);
+ }
+}
+
+void nsCSSFrameConstructor::ProcessChildren(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ ComputedStyle* aComputedStyle, nsContainerFrame* aFrame,
+ const bool aCanHaveGeneratedContent, nsFrameList& aFrameList,
+ const bool aAllowBlockStyles, nsIFrame* aPossiblyLeafFrame) {
+ MOZ_ASSERT(aFrame, "Must have parent frame here");
+ MOZ_ASSERT(aFrame->GetContentInsertionFrame() == aFrame,
+ "Parent frame in ProcessChildren should be its own "
+ "content insertion frame");
+
+ const uint32_t kMaxDepth = 2 * MAX_REFLOW_DEPTH;
+ static_assert(kMaxDepth <= UINT16_MAX, "mCurrentDepth type is too narrow");
+ AutoRestore<uint16_t> savedDepth(mCurrentDepth);
+ if (mCurrentDepth != UINT16_MAX) {
+ ++mCurrentDepth;
+ }
+
+ if (!aPossiblyLeafFrame) {
+ aPossiblyLeafFrame = aFrame;
+ }
+
+ // XXXbz ideally, this would do all the pushing of various
+ // containing blocks as needed, so callers don't have to do it...
+
+ // Check that our parent frame is a block before allowing ::first-letter/line.
+ // E.g. <button style="display:grid"> should not allow it.
+ const bool allowFirstPseudos =
+ aAllowBlockStyles && aFrame->IsBlockFrameOrSubclass();
+ bool haveFirstLetterStyle = false, haveFirstLineStyle = false;
+ if (allowFirstPseudos) {
+ ShouldHaveSpecialBlockStyle(aContent, aComputedStyle, &haveFirstLetterStyle,
+ &haveFirstLineStyle);
+ }
+
+ AutoFrameConstructionItemList itemsToConstruct(this);
+ AutoFrameConstructionPageName pageNameTracker(aState, aFrame);
+
+ // If we have first-letter or first-line style then frames can get
+ // moved around so don't set these flags.
+ if (allowFirstPseudos && !haveFirstLetterStyle && !haveFirstLineStyle) {
+ itemsToConstruct.SetLineBoundaryAtStart(true);
+ itemsToConstruct.SetLineBoundaryAtEnd(true);
+ }
+
+ // Create any anonymous frames we need here.
+ AutoTArray<nsIAnonymousContentCreator::ContentInfo, 4> anonymousItems;
+ GetAnonymousContent(aContent, aPossiblyLeafFrame, anonymousItems);
+#ifdef DEBUG
+ for (uint32_t i = 0; i < anonymousItems.Length(); ++i) {
+ MOZ_ASSERT(anonymousItems[i].mContent->IsRootOfNativeAnonymousSubtree(),
+ "Content should know it's an anonymous subtree");
+ }
+#endif
+ AddFCItemsForAnonymousContent(aState, aFrame, anonymousItems,
+ itemsToConstruct, pageNameTracker);
+
+ nsBlockFrame* listItem = nullptr;
+ bool isOutsideMarker = false;
+ if (!aPossiblyLeafFrame->IsLeaf()) {
+ // :before/:after content should have the same style parent as normal kids.
+ //
+ // Note that we don't use this style for looking up things like special
+ // block styles because in some cases involving table pseudo-frames it has
+ // nothing to do with the parent frame's desired behavior.
+ auto* styleParentFrame =
+ nsIFrame::CorrectStyleParentFrame(aFrame, PseudoStyleType::NotPseudo);
+ ComputedStyle* computedStyle = styleParentFrame->Style();
+
+ if (aCanHaveGeneratedContent) {
+ if (computedStyle->StyleDisplay()->IsListItem() &&
+ (listItem = do_QueryFrame(aFrame)) &&
+ !styleParentFrame->IsFieldSetFrame()) {
+ isOutsideMarker = computedStyle->StyleList()->mListStylePosition ==
+ StyleListStylePosition::Outside;
+ ItemFlags extraFlags;
+ if (isOutsideMarker) {
+ extraFlags += ItemFlag::IsForOutsideMarker;
+ }
+ CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
+ *computedStyle, PseudoStyleType::marker,
+ itemsToConstruct, extraFlags);
+ }
+ // Probe for generated content before
+ CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
+ *computedStyle, PseudoStyleType::before,
+ itemsToConstruct);
+ }
+
+ const bool addChildItems = MOZ_LIKELY(mCurrentDepth < kMaxDepth);
+ if (!addChildItems) {
+ NS_WARNING("ProcessChildren max depth exceeded");
+ }
+
+ FlattenedChildIterator iter(aContent);
+ const InsertionPoint insertion(aFrame, aContent);
+ for (nsIContent* child = iter.GetNextChild(); child;
+ child = iter.GetNextChild()) {
+ MOZ_ASSERT(insertion.mContainer == GetInsertionPoint(child).mContainer,
+ "GetInsertionPoint should agree with us");
+ if (addChildItems) {
+ AddFrameConstructionItems(aState, child, iter.ShadowDOMInvolved(),
+ *computedStyle, insertion, itemsToConstruct);
+ } else {
+ ClearLazyBits(child, child->GetNextSibling());
+ }
+ }
+ itemsToConstruct.SetParentHasNoShadowDOM(!iter.ShadowDOMInvolved());
+
+ if (aCanHaveGeneratedContent) {
+ // Probe for generated content after
+ CreateGeneratedContentItem(aState, aFrame, *aContent->AsElement(),
+ *computedStyle, PseudoStyleType::after,
+ itemsToConstruct);
+ }
+ } else {
+ ClearLazyBits(aContent->GetFirstChild(), nullptr);
+ }
+
+ ConstructFramesFromItemList(aState, itemsToConstruct, aFrame,
+ /* aParentIsWrapperAnonBox = */ false,
+ aFrameList);
+
+ if (listItem) {
+ if (auto* markerFrame = nsLayoutUtils::GetMarkerFrame(aContent)) {
+ for (auto* childFrame : aFrameList) {
+ if (markerFrame == childFrame) {
+ if (isOutsideMarker) {
+ // SetMarkerFrameForListItem will add childFrame to the
+ // FrameChildListID::Bullet
+ aFrameList.RemoveFrame(childFrame);
+ auto* grandParent = listItem->GetParent()->GetParent();
+ if (listItem->Style()->GetPseudoType() ==
+ PseudoStyleType::columnContent &&
+ grandParent && grandParent->IsColumnSetWrapperFrame()) {
+ listItem = do_QueryFrame(grandParent);
+ MOZ_ASSERT(listItem,
+ "ColumnSetWrapperFrame is expected to be "
+ "a nsBlockFrame subclass");
+ childFrame->SetParent(listItem);
+ }
+ }
+ listItem->SetMarkerFrameForListItem(childFrame);
+ MOZ_ASSERT(listItem->HasAnyStateBits(
+ NS_BLOCK_FRAME_HAS_OUTSIDE_MARKER) == isOutsideMarker);
+#ifdef ACCESSIBILITY
+ if (nsAccessibilityService* accService = GetAccService()) {
+ auto* marker = markerFrame->GetContent();
+ accService->ContentRangeInserted(mPresShell, marker, nullptr);
+ }
+#endif
+ break;
+ }
+ }
+ }
+ }
+
+ if (haveFirstLetterStyle) {
+ WrapFramesInFirstLetterFrame(aFrame, aFrameList);
+ }
+ if (haveFirstLineStyle) {
+ WrapFramesInFirstLineFrame(aState, aContent, aFrame, nullptr, aFrameList);
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Support for :first-line style
+
+// Special routine to handle placing a list of frames into a block
+// frame that has first-line style. The routine ensures that the first
+// collection of inline frames end up in a first-line frame.
+// NOTE: aState may have containing block information related to a
+// different part of the frame tree than where the first line occurs.
+// In particular aState may be set up for where ContentInserted or
+// ContentAppended is inserting content, which may be some
+// non-first-in-flow continuation of the block to which the first-line
+// belongs. So this function needs to be careful about how it uses
+// aState.
+void nsCSSFrameConstructor::WrapFramesInFirstLineFrame(
+ nsFrameConstructorState& aState, nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame, nsFirstLineFrame* aLineFrame,
+ nsFrameList& aFrameList) {
+ // Extract any initial inline frames from aFrameList so we can put them
+ // in the first-line.
+ nsFrameList firstLineChildren =
+ aFrameList.Split([](nsIFrame* f) { return !f->IsInlineOutside(); });
+
+ if (firstLineChildren.IsEmpty()) {
+ // Nothing is supposed to go into the first-line; nothing to do
+ return;
+ }
+
+ if (!aLineFrame) {
+ // Create line frame
+ ComputedStyle* parentStyle = nsIFrame::CorrectStyleParentFrame(
+ aBlockFrame, PseudoStyleType::firstLine)
+ ->Style();
+ RefPtr<ComputedStyle> firstLineStyle =
+ GetFirstLineStyle(aBlockContent, parentStyle);
+
+ aLineFrame = NS_NewFirstLineFrame(mPresShell, firstLineStyle);
+
+ // Initialize the line frame
+ InitAndRestoreFrame(aState, aBlockContent, aBlockFrame, aLineFrame);
+
+ // The lineFrame will be the block's first child; the rest of the
+ // frame list (after lastInlineFrame) will be the second and
+ // subsequent children; insert lineFrame into aFrameList.
+ aFrameList.InsertFrame(nullptr, nullptr, aLineFrame);
+
+ NS_ASSERTION(aLineFrame->Style() == firstLineStyle,
+ "Bogus style on line frame");
+ }
+
+ // Give the inline frames to the lineFrame <b>after</b> reparenting them
+ ReparentFrames(this, aLineFrame, firstLineChildren, true);
+ if (aLineFrame->PrincipalChildList().IsEmpty() &&
+ aLineFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
+ aLineFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(firstLineChildren));
+ } else {
+ AppendFrames(aLineFrame, FrameChildListID::Principal,
+ std::move(firstLineChildren));
+ }
+}
+
+// Special routine to handle appending a new frame to a block frame's
+// child list. Takes care of placing the new frame into the right
+// place when first-line style is present.
+void nsCSSFrameConstructor::AppendFirstLineFrames(
+ nsFrameConstructorState& aState, nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame, nsFrameList& aFrameList) {
+ // It's possible that aBlockFrame needs to have a first-line frame
+ // created because it doesn't currently have any children.
+ const nsFrameList& blockKids = aBlockFrame->PrincipalChildList();
+ if (blockKids.IsEmpty()) {
+ WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame, nullptr,
+ aFrameList);
+ return;
+ }
+
+ // Examine the last block child - if it's a first-line frame then
+ // appended frames need special treatment.
+ nsIFrame* lastBlockKid = blockKids.LastChild();
+ if (!lastBlockKid->IsLineFrame()) {
+ // No first-line frame at the end of the list, therefore there is
+ // an intervening block between any first-line frame the frames
+ // we are appending. Therefore, we don't need any special
+ // treatment of the appended frames.
+ return;
+ }
+
+ nsFirstLineFrame* lineFrame = static_cast<nsFirstLineFrame*>(lastBlockKid);
+ WrapFramesInFirstLineFrame(aState, aBlockContent, aBlockFrame, lineFrame,
+ aFrameList);
+}
+
+void nsCSSFrameConstructor::CheckForFirstLineInsertion(
+ nsIFrame* aParentFrame, nsFrameList& aFrameList) {
+ MOZ_ASSERT(aParentFrame->Style()->HasPseudoElementData(),
+ "Why were we called?");
+
+ if (aFrameList.IsEmpty()) {
+ // Happens often enough, with the caption stuff. No need to do the ancestor
+ // walk here.
+ return;
+ }
+
+ class RestyleManager* restyleManager = RestyleManager();
+
+ // Check whether there's a ::first-line on the path up from aParentFrame.
+ // Note that we can't stop until we've run out of ancestors with
+ // pseudo-element data, because the first-letter might be somewhere way up the
+ // tree; in particular it might be past our containing block.
+ nsIFrame* ancestor = aParentFrame;
+ while (ancestor) {
+ if (!ancestor->Style()->HasPseudoElementData()) {
+ // We know we won't find a ::first-line now.
+ return;
+ }
+
+ if (!ancestor->IsLineFrame()) {
+ ancestor = ancestor->GetParent();
+ continue;
+ }
+
+ if (!ancestor->Style()->IsPseudoElement()) {
+ // This is a continuation lineframe, not the first line; no need to do
+ // anything to the styles.
+ return;
+ }
+
+ // Fix up the styles of aFrameList for ::first-line.
+ for (nsIFrame* f : aFrameList) {
+ restyleManager->ReparentComputedStyleForFirstLine(f);
+ }
+ return;
+ }
+}
+
+//----------------------------------------------------------------------
+
+// First-letter support
+
+// Determine how many characters in the text fragment apply to the
+// first letter
+static int32_t FirstLetterCount(const nsTextFragment* aFragment) {
+ int32_t count = 0;
+ int32_t firstLetterLength = 0;
+
+ const uint32_t n = aFragment->GetLength();
+ for (uint32_t i = 0; i < n; i++) {
+ const char16_t ch = aFragment->CharAt(i);
+ // FIXME: take content language into account when deciding whitespace.
+ if (dom::IsSpaceCharacter(ch)) {
+ if (firstLetterLength) {
+ break;
+ }
+ count++;
+ continue;
+ }
+ // XXX I18n
+ if ((ch == '\'') || (ch == '\"')) {
+ if (firstLetterLength) {
+ break;
+ }
+ // keep looping
+ firstLetterLength = 1;
+ } else {
+ count++;
+ break;
+ }
+ }
+
+ return count;
+}
+
+static bool NeedFirstLetterContinuation(Text* aText) {
+ MOZ_ASSERT(aText, "null ptr");
+ int32_t flc = FirstLetterCount(&aText->TextFragment());
+ int32_t tl = aText->TextDataLength();
+ return flc < tl;
+}
+
+static bool IsFirstLetterContent(Text* aText) {
+ return aText->TextDataLength() && !aText->TextIsOnlyWhitespace();
+}
+
+/**
+ * Create a letter frame, only make it a floating frame.
+ */
+nsFirstLetterFrame* nsCSSFrameConstructor::CreateFloatingLetterFrame(
+ nsFrameConstructorState& aState, Text* aTextContent, nsIFrame* aTextFrame,
+ nsContainerFrame* aParentFrame, ComputedStyle* aParentStyle,
+ ComputedStyle* aComputedStyle, nsFrameList& aResult) {
+ MOZ_ASSERT(aParentStyle);
+
+ nsFirstLetterFrame* letterFrame =
+ NS_NewFloatingFirstLetterFrame(mPresShell, aComputedStyle);
+ // We don't want to use a text content for a non-text frame (because we want
+ // its primary frame to be a text frame).
+ nsIContent* letterContent = aParentFrame->GetContent();
+ nsContainerFrame* containingBlock =
+ aState.GetGeometricParent(*aComputedStyle->StyleDisplay(), aParentFrame);
+ InitAndRestoreFrame(aState, letterContent, containingBlock, letterFrame);
+
+ // Init the text frame to refer to the letter frame.
+ //
+ // Make sure we get a proper style for it (the one passed in is for the letter
+ // frame and will have the float property set on it; the text frame shouldn't
+ // have that set).
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ RefPtr<ComputedStyle> textSC =
+ styleSet->ResolveStyleForText(aTextContent, aComputedStyle);
+ aTextFrame->SetComputedStyleWithoutNotification(textSC);
+ InitAndRestoreFrame(aState, aTextContent, letterFrame, aTextFrame);
+
+ // And then give the text frame to the letter frame
+ SetInitialSingleChild(letterFrame, aTextFrame);
+
+ // See if we will need to continue the text frame (does it contain
+ // more than just the first-letter text or not?) If it does, then we
+ // create (in advance) a continuation frame for it.
+ nsIFrame* nextTextFrame = nullptr;
+ if (NeedFirstLetterContinuation(aTextContent)) {
+ // Create continuation
+ nextTextFrame = CreateContinuingFrame(aTextFrame, aParentFrame);
+ RefPtr<ComputedStyle> newSC =
+ styleSet->ResolveStyleForText(aTextContent, aParentStyle);
+ nextTextFrame->SetComputedStyle(newSC);
+ }
+
+ NS_ASSERTION(aResult.IsEmpty(), "aResult should be an empty nsFrameList!");
+ // Put the new float before any of the floats in the block we're doing
+ // first-letter for, that is, before any floats whose parent is
+ // containingBlock.
+ nsIFrame* prevSibling = nullptr;
+ for (nsIFrame* f : aState.mFloatedList) {
+ if (f->GetParent() == containingBlock) {
+ break;
+ }
+ prevSibling = f;
+ }
+
+ aState.AddChild(letterFrame, aResult, letterContent, aParentFrame, false,
+ true, true, prevSibling);
+
+ if (nextTextFrame) {
+ aResult.AppendFrame(nullptr, nextTextFrame);
+ }
+
+ return letterFrame;
+}
+
+/**
+ * Create a new letter frame for aTextFrame. The letter frame will be
+ * a child of aParentFrame.
+ */
+void nsCSSFrameConstructor::CreateLetterFrame(
+ nsContainerFrame* aBlockFrame, nsContainerFrame* aBlockContinuation,
+ Text* aTextContent, nsContainerFrame* aParentFrame, nsFrameList& aResult) {
+ NS_ASSERTION(aBlockFrame->IsBlockFrameOrSubclass(), "Not a block frame?");
+
+ // Get a ComputedStyle for the first-letter-frame.
+ //
+ // Keep this in sync with nsBlockFrame::UpdatePseudoElementStyles.
+ nsIFrame* parentFrame = nsIFrame::CorrectStyleParentFrame(
+ aParentFrame, PseudoStyleType::firstLetter);
+
+ ComputedStyle* parentComputedStyle = parentFrame->Style();
+ ComputedStyle* parentComputedStyleIgnoringFirstLine = parentComputedStyle;
+ if (parentFrame->IsLineFrame()) {
+ parentComputedStyleIgnoringFirstLine =
+ nsIFrame::CorrectStyleParentFrame(aBlockFrame,
+ PseudoStyleType::firstLetter)
+ ->Style();
+ }
+
+ // Use content from containing block so that we can actually
+ // find a matching style rule.
+ nsIContent* blockContent = aBlockFrame->GetContent();
+
+ // Create first-letter style rule, ignoring first line. If we already have a
+ // first-line we'll reparent the style below.
+ RefPtr<ComputedStyle> sc =
+ GetFirstLetterStyle(blockContent, parentComputedStyleIgnoringFirstLine);
+
+ if (sc) {
+ if (parentComputedStyleIgnoringFirstLine != parentComputedStyle) {
+ sc = mPresShell->StyleSet()->ReparentComputedStyle(
+ sc, parentComputedStyle, parentComputedStyle,
+ blockContent->AsElement());
+ }
+
+ RefPtr<ComputedStyle> textSC =
+ mPresShell->StyleSet()->ResolveStyleForText(aTextContent, sc);
+
+ // Create a new text frame (the original one will be discarded)
+ // pass a temporary stylecontext, the correct one will be set
+ // later. Start off by unsetting the primary frame for
+ // aTextContent, so it's no longer pointing to the to-be-destroyed
+ // frame.
+ // XXXbz it would be really nice to destroy the old frame _first_,
+ // then create the new one, so we could avoid this hack.
+ aTextContent->SetPrimaryFrame(nullptr);
+ nsIFrame* textFrame = NS_NewTextFrame(mPresShell, textSC);
+
+ NS_ASSERTION(aBlockContinuation == GetFloatContainingBlock(aParentFrame),
+ "Containing block is confused");
+ nsFrameConstructorState state(
+ mPresShell, GetAbsoluteContainingBlock(aParentFrame, FIXED_POS),
+ GetAbsoluteContainingBlock(aParentFrame, ABS_POS), aBlockContinuation);
+
+ // Create the right type of first-letter frame
+ const nsStyleDisplay* display = sc->StyleDisplay();
+ nsFirstLetterFrame* letterFrame;
+ if (display->IsFloatingStyle() && !aParentFrame->IsInSVGTextSubtree()) {
+ // Make a floating first-letter frame
+ letterFrame = CreateFloatingLetterFrame(state, aTextContent, textFrame,
+ aParentFrame, parentComputedStyle,
+ sc, aResult);
+ } else {
+ // Make an inflow first-letter frame
+ letterFrame = NS_NewFirstLetterFrame(mPresShell, sc);
+
+ // Initialize the first-letter-frame. We don't want to use a text
+ // content for a non-text frame (because we want its primary frame to
+ // be a text frame).
+ nsIContent* letterContent = aParentFrame->GetContent();
+ letterFrame->Init(letterContent, aParentFrame, nullptr);
+
+ InitAndRestoreFrame(state, aTextContent, letterFrame, textFrame);
+
+ SetInitialSingleChild(letterFrame, textFrame);
+ aResult.Clear();
+ aResult.AppendFrame(nullptr, letterFrame);
+ NS_ASSERTION(!aBlockFrame->GetPrevContinuation(),
+ "should have the first continuation here");
+ aBlockFrame->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD);
+ }
+ MOZ_ASSERT(
+ !aBlockFrame->GetPrevContinuation(),
+ "Setting up a first-letter frame on a non-first block continuation?");
+ auto parent =
+ static_cast<nsContainerFrame*>(aParentFrame->FirstContinuation());
+ if (MOZ_UNLIKELY(parent->IsLineFrame())) {
+ parent = static_cast<nsContainerFrame*>(
+ parent->GetParent()->FirstContinuation());
+ }
+ parent->SetHasFirstLetterChild();
+ aBlockFrame->SetProperty(nsContainerFrame::FirstLetterProperty(),
+ letterFrame);
+ aTextContent->SetPrimaryFrame(textFrame);
+ }
+}
+
+void nsCSSFrameConstructor::WrapFramesInFirstLetterFrame(
+ nsContainerFrame* aBlockFrame, nsFrameList& aBlockFrames) {
+ aBlockFrame->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
+
+ nsContainerFrame* parentFrame = nullptr;
+ nsIFrame* textFrame = nullptr;
+ nsIFrame* prevFrame = nullptr;
+ nsFrameList letterFrames;
+ bool stopLooking = false;
+ WrapFramesInFirstLetterFrame(
+ aBlockFrame, aBlockFrame, aBlockFrame, aBlockFrames.FirstChild(),
+ &parentFrame, &textFrame, &prevFrame, letterFrames, &stopLooking);
+ if (!parentFrame) {
+ return;
+ }
+ DestroyContext context(mPresShell);
+ if (parentFrame == aBlockFrame) {
+ // Take textFrame out of the block's frame list and substitute the
+ // letter frame(s) instead.
+ aBlockFrames.DestroyFrame(context, textFrame);
+ aBlockFrames.InsertFrames(nullptr, prevFrame, std::move(letterFrames));
+ } else {
+ // Take the old textFrame out of the inline parent's child list
+ RemoveFrame(context, FrameChildListID::Principal, textFrame);
+
+ // Insert in the letter frame(s)
+ parentFrame->InsertFrames(FrameChildListID::Principal, prevFrame, nullptr,
+ std::move(letterFrames));
+ }
+}
+
+void nsCSSFrameConstructor::WrapFramesInFirstLetterFrame(
+ nsContainerFrame* aBlockFrame, nsContainerFrame* aBlockContinuation,
+ nsContainerFrame* aParentFrame, nsIFrame* aParentFrameList,
+ nsContainerFrame** aModifiedParent, nsIFrame** aTextFrame,
+ nsIFrame** aPrevFrame, nsFrameList& aLetterFrames, bool* aStopLooking) {
+ nsIFrame* prevFrame = nullptr;
+ nsIFrame* frame = aParentFrameList;
+
+ // This loop attempts to implement "Finding the First Letter":
+ // https://drafts.csswg.org/css-pseudo-4/#application-in-css
+ // FIXME: we don't handle nested blocks correctly yet though (bug 214004)
+ while (frame) {
+ nsIFrame* nextFrame = frame->GetNextSibling();
+
+ // Skip all ::markers and placeholders.
+ if (frame->Style()->GetPseudoType() == PseudoStyleType::marker ||
+ frame->IsPlaceholderFrame()) {
+ prevFrame = frame;
+ frame = nextFrame;
+ continue;
+ }
+ LayoutFrameType frameType = frame->Type();
+ if (LayoutFrameType::Text == frameType) {
+ // Wrap up first-letter content in a letter frame
+ Text* textContent = frame->GetContent()->AsText();
+ if (IsFirstLetterContent(textContent)) {
+ // Create letter frame to wrap up the text
+ CreateLetterFrame(aBlockFrame, aBlockContinuation, textContent,
+ aParentFrame, aLetterFrames);
+
+ // Provide adjustment information for parent
+ *aModifiedParent = aParentFrame;
+ *aTextFrame = frame;
+ *aPrevFrame = prevFrame;
+ *aStopLooking = true;
+ return;
+ }
+ } else if (IsInlineFrame(frame) && frameType != LayoutFrameType::Br) {
+ nsIFrame* kids = frame->PrincipalChildList().FirstChild();
+ WrapFramesInFirstLetterFrame(aBlockFrame, aBlockContinuation,
+ static_cast<nsContainerFrame*>(frame), kids,
+ aModifiedParent, aTextFrame, aPrevFrame,
+ aLetterFrames, aStopLooking);
+ if (*aStopLooking) {
+ return;
+ }
+ } else {
+ // This will stop us looking to create more letter frames. For
+ // example, maybe the frame-type is "letterFrame" or
+ // "placeholderFrame". This keeps us from creating extra letter
+ // frames, and also prevents us from creating letter frames when
+ // the first real content child of a block is not text (e.g. an
+ // image, hr, etc.)
+ *aStopLooking = true;
+ break;
+ }
+
+ prevFrame = frame;
+ frame = nextFrame;
+ }
+}
+
+static nsIFrame* FindFirstLetterFrame(nsIFrame* aFrame,
+ FrameChildListID aListID) {
+ for (nsIFrame* f : aFrame->GetChildList(aListID)) {
+ if (f->IsLetterFrame()) {
+ return f;
+ }
+ }
+ return nullptr;
+}
+
+static void ClearHasFirstLetterChildFrom(nsContainerFrame* aParentFrame) {
+ MOZ_ASSERT(aParentFrame);
+ auto* parent =
+ static_cast<nsContainerFrame*>(aParentFrame->FirstContinuation());
+ if (MOZ_UNLIKELY(parent->IsLineFrame())) {
+ MOZ_ASSERT(!parent->HasFirstLetterChild());
+ parent = static_cast<nsContainerFrame*>(
+ parent->GetParent()->FirstContinuation());
+ }
+ MOZ_ASSERT(parent->HasFirstLetterChild());
+ parent->ClearHasFirstLetterChild();
+}
+
+void nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames(
+ PresShell* aPresShell, nsIFrame* aBlockFrame) {
+ // Look for the first letter frame on the FrameChildListID::Float, then
+ // FrameChildListID::PushedFloats.
+ nsIFrame* floatFrame =
+ ::FindFirstLetterFrame(aBlockFrame, FrameChildListID::Float);
+ if (!floatFrame) {
+ floatFrame =
+ ::FindFirstLetterFrame(aBlockFrame, FrameChildListID::PushedFloats);
+ if (!floatFrame) {
+ return;
+ }
+ }
+
+ // Take the text frame away from the letter frame (so it isn't
+ // destroyed when we destroy the letter frame).
+ nsIFrame* textFrame = floatFrame->PrincipalChildList().FirstChild();
+ if (!textFrame) {
+ return;
+ }
+
+ // Discover the placeholder frame for the letter frame
+ nsPlaceholderFrame* placeholderFrame = floatFrame->GetPlaceholderFrame();
+ if (!placeholderFrame) {
+ // Somethings really wrong
+ return;
+ }
+ nsContainerFrame* parentFrame = placeholderFrame->GetParent();
+ if (!parentFrame) {
+ // Somethings really wrong
+ return;
+ }
+
+ ClearHasFirstLetterChildFrom(parentFrame);
+
+ // Create a new text frame with the right style that maps all of the content
+ // that was previously part of the letter frame (and probably continued
+ // elsewhere).
+ ComputedStyle* parentSC = parentFrame->Style();
+ nsIContent* textContent = textFrame->GetContent();
+ if (!textContent) {
+ return;
+ }
+ RefPtr<ComputedStyle> newSC =
+ aPresShell->StyleSet()->ResolveStyleForText(textContent, parentSC);
+ nsIFrame* newTextFrame = NS_NewTextFrame(aPresShell, newSC);
+ newTextFrame->Init(textContent, parentFrame, nullptr);
+
+ // Destroy the old text frame's continuations (the old text frame
+ // will be destroyed when its letter frame is destroyed).
+ nsIFrame* frameToDelete = textFrame->LastContinuation();
+ DestroyContext context(mPresShell);
+ while (frameToDelete != textFrame) {
+ nsIFrame* nextFrameToDelete = frameToDelete->GetPrevContinuation();
+ RemoveFrame(context, FrameChildListID::Principal, frameToDelete);
+ frameToDelete = nextFrameToDelete;
+ }
+
+ nsIFrame* prevSibling = placeholderFrame->GetPrevSibling();
+
+ // Now that everything is set...
+#ifdef NOISY_FIRST_LETTER
+ printf(
+ "RemoveFloatingFirstLetterFrames: textContent=%p oldTextFrame=%p "
+ "newTextFrame=%p\n",
+ textContent.get(), textFrame, newTextFrame);
+#endif
+
+ // Remove placeholder frame and the float
+ RemoveFrame(context, FrameChildListID::Principal, placeholderFrame);
+
+ // Now that the old frames are gone, we can start pointing to our
+ // new primary frame.
+ textContent->SetPrimaryFrame(newTextFrame);
+
+ // Wallpaper bug 822910.
+ bool offsetsNeedFixing = prevSibling && prevSibling->IsTextFrame();
+ if (offsetsNeedFixing) {
+ prevSibling->AddStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ // Insert text frame in its place
+ InsertFrames(parentFrame, FrameChildListID::Principal, prevSibling,
+ nsFrameList(newTextFrame, newTextFrame));
+
+ if (offsetsNeedFixing) {
+ prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+}
+
+void nsCSSFrameConstructor::RemoveFirstLetterFrames(
+ PresShell* aPresShell, nsContainerFrame* aFrame,
+ nsContainerFrame* aBlockFrame, bool* aStopLooking) {
+ nsIFrame* prevSibling = nullptr;
+ nsIFrame* kid = aFrame->PrincipalChildList().FirstChild();
+
+ while (kid) {
+ if (kid->IsLetterFrame()) {
+ ClearHasFirstLetterChildFrom(aFrame);
+ nsIFrame* textFrame = kid->PrincipalChildList().FirstChild();
+ if (!textFrame) {
+ break;
+ }
+
+ // Create a new textframe
+ ComputedStyle* parentSC = aFrame->Style();
+ if (!parentSC) {
+ break;
+ }
+ nsIContent* textContent = textFrame->GetContent();
+ if (!textContent) {
+ break;
+ }
+ RefPtr<ComputedStyle> newSC =
+ aPresShell->StyleSet()->ResolveStyleForText(textContent, parentSC);
+ textFrame = NS_NewTextFrame(aPresShell, newSC);
+ textFrame->Init(textContent, aFrame, nullptr);
+
+ DestroyContext context(mPresShell);
+
+ // Next rip out the kid and replace it with the text frame
+ RemoveFrame(context, FrameChildListID::Principal, kid);
+
+ // Now that the old frames are gone, we can start pointing to our
+ // new primary frame.
+ textContent->SetPrimaryFrame(textFrame);
+
+ // Wallpaper bug 822910.
+ bool offsetsNeedFixing = prevSibling && prevSibling->IsTextFrame();
+ if (offsetsNeedFixing) {
+ prevSibling->AddStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ // Insert text frame in its place
+ InsertFrames(aFrame, FrameChildListID::Principal, prevSibling,
+ nsFrameList(textFrame, textFrame));
+
+ if (offsetsNeedFixing) {
+ prevSibling->RemoveStateBits(TEXT_OFFSETS_NEED_FIXING);
+ }
+
+ *aStopLooking = true;
+ NS_ASSERTION(!aBlockFrame->GetPrevContinuation(),
+ "should have the first continuation here");
+ aBlockFrame->RemoveStateBits(NS_BLOCK_HAS_FIRST_LETTER_CHILD);
+ break;
+ }
+ if (IsInlineFrame(kid)) {
+ nsContainerFrame* kidAsContainerFrame = do_QueryFrame(kid);
+ if (kidAsContainerFrame) {
+ // Look inside child inline frame for the letter frame.
+ RemoveFirstLetterFrames(aPresShell, kidAsContainerFrame, aBlockFrame,
+ aStopLooking);
+ if (*aStopLooking) {
+ break;
+ }
+ }
+ }
+ prevSibling = kid;
+ kid = kid->GetNextSibling();
+ }
+}
+
+void nsCSSFrameConstructor::RemoveLetterFrames(PresShell* aPresShell,
+ nsContainerFrame* aBlockFrame) {
+ aBlockFrame =
+ static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
+ aBlockFrame->RemoveProperty(nsContainerFrame::FirstLetterProperty());
+ nsContainerFrame* continuation = aBlockFrame;
+
+ bool stopLooking = false;
+ do {
+ RemoveFloatingFirstLetterFrames(aPresShell, continuation);
+ RemoveFirstLetterFrames(aPresShell, continuation, aBlockFrame,
+ &stopLooking);
+ if (stopLooking) {
+ break;
+ }
+ continuation =
+ static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
+ } while (continuation);
+}
+
+// Fixup the letter frame situation for the given block
+void nsCSSFrameConstructor::RecoverLetterFrames(nsContainerFrame* aBlockFrame) {
+ aBlockFrame =
+ static_cast<nsContainerFrame*>(aBlockFrame->FirstContinuation());
+ nsContainerFrame* continuation = aBlockFrame;
+
+ nsContainerFrame* parentFrame = nullptr;
+ nsIFrame* textFrame = nullptr;
+ nsIFrame* prevFrame = nullptr;
+ nsFrameList letterFrames;
+ bool stopLooking = false;
+ do {
+ // XXX shouldn't this bit be set already (bug 408493), assert instead?
+ continuation->AddStateBits(NS_BLOCK_HAS_FIRST_LETTER_STYLE);
+ WrapFramesInFirstLetterFrame(
+ aBlockFrame, continuation, continuation,
+ continuation->PrincipalChildList().FirstChild(), &parentFrame,
+ &textFrame, &prevFrame, letterFrames, &stopLooking);
+ if (stopLooking) {
+ break;
+ }
+ continuation =
+ static_cast<nsContainerFrame*>(continuation->GetNextContinuation());
+ } while (continuation);
+
+ if (!parentFrame) {
+ return;
+ }
+ // Take the old textFrame out of the parent's child list
+ DestroyContext context(mPresShell);
+ RemoveFrame(context, FrameChildListID::Principal, textFrame);
+
+ // Insert in the letter frame(s)
+ parentFrame->InsertFrames(FrameChildListID::Principal, prevFrame, nullptr,
+ std::move(letterFrames));
+}
+
+//----------------------------------------------------------------------
+
+void nsCSSFrameConstructor::ConstructBlock(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, nsContainerFrame* aContentParentFrame,
+ ComputedStyle* aComputedStyle, nsContainerFrame** aNewFrame,
+ nsFrameList& aFrameList, nsIFrame* aPositionedFrameForAbsPosContainer) {
+ // clang-format off
+ //
+ // If a block frame is in a multi-column subtree, its children may need to
+ // be chopped into runs of blocks containing column-spans and runs of
+ // blocks containing no column-spans. Each run containing column-spans
+ // will be wrapped by an anonymous block. See CreateColumnSpanSiblings() for
+ // the implementation.
+ //
+ // If a block frame is a multi-column container, its children will need to
+ // be processed as above. Moreover, it creates a ColumnSetWrapperFrame as
+ // its outermost frame, and its children which have no
+ // -moz-column-span-wrapper pseudo will be wrapped in ColumnSetFrames. See
+ // FinishBuildingColumns() for the implementation.
+ //
+ // The multi-column subtree maintains the following invariants:
+ //
+ // 1) All the frames have the frame state bit
+ // NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR set, except for top-level
+ // ColumnSetWrapperFrame and those children in the column-span subtrees.
+ //
+ // 2) The first and last frame under ColumnSetWrapperFrame are always
+ // ColumnSetFrame.
+ //
+ // 3) ColumnSetFrames are linked together as continuations.
+ //
+ // 4) Those column-span wrappers are *not* linked together with themselves nor
+ // with the original block frame. The continuation chain consists of the
+ // original block frame and the original block's continuations wrapping
+ // non-column-spans.
+ //
+ // For example, this HTML
+ // <div id="x" style="column-count: 2;">
+ // <div style="column-span: all">a</div>
+ // <div id="y">
+ // b
+ // <div style="column-span: all">c</div>
+ // <div style="column-span: all">d</div>
+ // e
+ // </div>
+ // </div>
+ // <div style="column-span: all">f</div>
+ //
+ // yields the following frame tree.
+ //
+ // A) ColumnSetWrapper (original style)
+ // B) ColumnSet (-moz-column-set) <-- always created by BeginBuildingColumns
+ // C) Block (-moz-column-content)
+ // D) Block (-moz-column-span-wrapper, created by x)
+ // E) Block (div)
+ // F) Text ("a")
+ // G) ColumnSet (-moz-column-set)
+ // H) Block (-moz-column-content, created by x)
+ // I) Block (div, y)
+ // J) Text ("b")
+ // K) Block (-moz-column-span-wrapper, created by x)
+ // L) Block (-moz-column-span-wrapper, created by y)
+ // M) Block (div, new BFC)
+ // N) Text ("c")
+ // O) Block (div, new BFC)
+ // P) Text ("d")
+ // Q) ColumnSet (-moz-column-set)
+ // R) Block (-moz-column-content, created by x)
+ // S) Block (div, y)
+ // T) Text ("e")
+ // U) Block (div, new BFC) <-- not in multi-column hierarchy
+ // V) Text ("f")
+ //
+ // ColumnSet linkage described in 3): B -> G -> Q
+ //
+ // Block linkage described in 4): C -> H -> R and I -> S
+ //
+ // clang-format on
+
+ nsBlockFrame* blockFrame = do_QueryFrame(*aNewFrame);
+ MOZ_ASSERT(blockFrame && blockFrame->IsBlockFrame(), "not a block frame?");
+
+ // Create column hierarchy if necessary.
+ const bool needsColumn =
+ aComputedStyle->StyleColumn()->IsColumnContainerStyle();
+ if (needsColumn) {
+ *aNewFrame = BeginBuildingColumns(aState, aContent, aParentFrame,
+ blockFrame, aComputedStyle);
+
+ if (aPositionedFrameForAbsPosContainer == blockFrame) {
+ aPositionedFrameForAbsPosContainer = *aNewFrame;
+ }
+ } else {
+ // No need to create column hierarchy. Initialize block frame.
+ blockFrame->SetComputedStyleWithoutNotification(aComputedStyle);
+ InitAndRestoreFrame(aState, aContent, aParentFrame, blockFrame);
+ }
+
+ aState.AddChild(*aNewFrame, aFrameList, aContent,
+ aContentParentFrame ? aContentParentFrame : aParentFrame);
+ if (!mRootElementFrame) {
+ mRootElementFrame = *aNewFrame;
+ }
+
+ // We should make the outer frame be the absolute containing block,
+ // if one is required. We have to do this because absolute
+ // positioning must be computed with respect to the CSS dimensions
+ // of the element, which are the dimensions of the outer block. But
+ // we can't really do that because only blocks can have absolute
+ // children. So use the block and try to compensate with hacks
+ // in nsBlockFrame::CalculateContainingBlockSizeForAbsolutes.
+ nsFrameConstructorSaveState absoluteSaveState;
+ (*aNewFrame)->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (aPositionedFrameForAbsPosContainer) {
+ aState.PushAbsoluteContainingBlock(
+ *aNewFrame, aPositionedFrameForAbsPosContainer, absoluteSaveState);
+ }
+
+ nsFrameConstructorSaveState floatSaveState;
+ aState.MaybePushFloatContainingBlock(blockFrame, floatSaveState);
+
+ if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
+ !ShouldSuppressColumnSpanDescendants(aParentFrame)) {
+ blockFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+ }
+
+ // Process the child content
+ nsFrameList childList;
+ ProcessChildren(aState, aContent, aComputedStyle, blockFrame, true, childList,
+ true);
+
+ if (!MayNeedToCreateColumnSpanSiblings(blockFrame, childList)) {
+ // No need to create column-span siblings.
+ blockFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ return;
+ }
+
+ // Extract any initial non-column-span kids, and put them in block frame's
+ // child list.
+ nsFrameList initialNonColumnSpanKids =
+ childList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+ blockFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(initialNonColumnSpanKids));
+
+ if (childList.IsEmpty()) {
+ // No more kids to process (there weren't any column-span kids).
+ return;
+ }
+
+ nsFrameList columnSpanSiblings = CreateColumnSpanSiblings(
+ aState, blockFrame, childList,
+ // If we're constructing a column container, pass nullptr as
+ // aPositionedFrame to forbid reparenting absolute/fixed positioned frames
+ // to column contents or column-span wrappers.
+ needsColumn ? nullptr : aPositionedFrameForAbsPosContainer);
+
+ if (needsColumn) {
+ // We're constructing a column container; need to finish building it.
+ FinishBuildingColumns(aState, *aNewFrame, blockFrame, columnSpanSiblings);
+ } else {
+ // We're constructing a normal block which has column-span children in a
+ // column hierarchy such as "x" in the following example.
+ //
+ // <div style="column-count: 2">
+ // <div id="x">
+ // <div>normal child</div>
+ // <div style="column-span">spanner</div>
+ // </div>
+ // </div>
+ aFrameList.AppendFrames(nullptr, std::move(columnSpanSiblings));
+ }
+
+ MOZ_ASSERT(columnSpanSiblings.IsEmpty(),
+ "The column-span siblings should be moved to the proper place!");
+}
+
+nsBlockFrame* nsCSSFrameConstructor::BeginBuildingColumns(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame, nsContainerFrame* aColumnContent,
+ ComputedStyle* aComputedStyle) {
+ MOZ_ASSERT(aColumnContent->IsBlockFrame(),
+ "aColumnContent should be a block frame.");
+ MOZ_ASSERT(aComputedStyle->StyleColumn()->IsColumnContainerStyle(),
+ "No need to build a column hierarchy!");
+
+ // The initial column hierarchy looks like this:
+ //
+ // ColumnSetWrapper (original style)
+ // ColumnSet (-moz-column-set)
+ // Block (-moz-column-content)
+ //
+ nsBlockFrame* columnSetWrapper = NS_NewColumnSetWrapperFrame(
+ mPresShell, aComputedStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES));
+ InitAndRestoreFrame(aState, aContent, aParentFrame, columnSetWrapper);
+ if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
+ !ShouldSuppressColumnSpanDescendants(aParentFrame)) {
+ columnSetWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+ }
+
+ AutoFrameConstructionPageName pageNameTracker(aState, columnSetWrapper);
+ RefPtr<ComputedStyle> columnSetStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::columnSet, aComputedStyle);
+ nsContainerFrame* columnSet = NS_NewColumnSetFrame(
+ mPresShell, columnSetStyle, nsFrameState(NS_FRAME_OWNS_ANON_BOXES));
+ InitAndRestoreFrame(aState, aContent, columnSetWrapper, columnSet);
+ columnSet->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+
+ RefPtr<ComputedStyle> blockStyle =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::columnContent, columnSetStyle);
+ aColumnContent->SetComputedStyleWithoutNotification(blockStyle);
+ InitAndRestoreFrame(aState, aContent, columnSet, aColumnContent);
+ aColumnContent->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+
+ // Set up the parent-child chain.
+ SetInitialSingleChild(columnSetWrapper, columnSet);
+ SetInitialSingleChild(columnSet, aColumnContent);
+
+ return columnSetWrapper;
+}
+
+void nsCSSFrameConstructor::FinishBuildingColumns(
+ nsFrameConstructorState& aState, nsContainerFrame* aColumnSetWrapper,
+ nsContainerFrame* aColumnContent, nsFrameList& aColumnContentSiblings) {
+ nsContainerFrame* prevColumnSet = aColumnContent->GetParent();
+
+ MOZ_ASSERT(prevColumnSet->IsColumnSetFrame() &&
+ prevColumnSet->GetParent() == aColumnSetWrapper,
+ "Should have established column hierarchy!");
+
+ // Tag the first ColumnSet to have column-span siblings so that the bit can
+ // propagate to all the continuations. We don't want the last ColumnSet to
+ // have this bit, so we will unset the bit for it at the end of this function.
+ prevColumnSet->SetHasColumnSpanSiblings(true);
+
+ nsFrameList finalList;
+ while (aColumnContentSiblings.NotEmpty()) {
+ nsIFrame* f = aColumnContentSiblings.RemoveFirstChild();
+ if (f->IsColumnSpan()) {
+ // Do nothing for column-span wrappers. Just move it to the final
+ // items.
+ finalList.AppendFrame(aColumnSetWrapper, f);
+ } else {
+ auto* continuingColumnSet = static_cast<nsContainerFrame*>(
+ CreateContinuingFrame(prevColumnSet, aColumnSetWrapper, false));
+ MOZ_ASSERT(continuingColumnSet->HasColumnSpanSiblings(),
+ "The bit should propagate to the next continuation!");
+
+ f->SetParent(continuingColumnSet);
+ SetInitialSingleChild(continuingColumnSet, f);
+ finalList.AppendFrame(aColumnSetWrapper, continuingColumnSet);
+ prevColumnSet = continuingColumnSet;
+ }
+ }
+
+ // Unset the bit because the last ColumnSet has no column-span siblings.
+ prevColumnSet->SetHasColumnSpanSiblings(false);
+
+ aColumnSetWrapper->AppendFrames(FrameChildListID::Principal,
+ std::move(finalList));
+}
+
+bool nsCSSFrameConstructor::MayNeedToCreateColumnSpanSiblings(
+ nsContainerFrame* aBlockFrame, const nsFrameList& aChildList) {
+ if (!aBlockFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ // The block frame isn't in a multi-column block formatting context.
+ return false;
+ }
+
+ if (ShouldSuppressColumnSpanDescendants(aBlockFrame)) {
+ // No need to create column-span siblings for a frame that suppresses them.
+ return false;
+ }
+
+ if (aChildList.IsEmpty()) {
+ // No child needs to be processed.
+ return false;
+ }
+
+ // Need to actually look into the child list.
+ return true;
+}
+
+nsFrameList nsCSSFrameConstructor::CreateColumnSpanSiblings(
+ nsFrameConstructorState& aState, nsContainerFrame* aInitialBlock,
+ nsFrameList& aChildList, nsIFrame* aPositionedFrame) {
+ MOZ_ASSERT(aInitialBlock->IsBlockFrameOrSubclass());
+ MOZ_ASSERT(!aPositionedFrame || aPositionedFrame->IsAbsPosContainingBlock());
+
+ nsIContent* const content = aInitialBlock->GetContent();
+ nsContainerFrame* const parentFrame = aInitialBlock->GetParent();
+ const bool isInitialBlockFloatCB = aInitialBlock->IsFloatContainingBlock();
+
+ nsFrameList siblings;
+ nsContainerFrame* lastNonColumnSpanWrapper = aInitialBlock;
+
+ // Tag the first non-column-span wrapper to have column-span siblings so that
+ // the bit can propagate to all the continuations. We don't want the last
+ // wrapper to have this bit, so we will unset the bit for it at the end of
+ // this function.
+ lastNonColumnSpanWrapper->SetHasColumnSpanSiblings(true);
+ do {
+ MOZ_ASSERT(aChildList.NotEmpty(), "Why call this if child list is empty?");
+ MOZ_ASSERT(aChildList.FirstChild()->IsColumnSpan(),
+ "Must have the child starting with column-span!");
+
+ // Grab the consecutive column-span kids, and reparent them into a
+ // block frame.
+ RefPtr<ComputedStyle> columnSpanWrapperStyle =
+ mPresShell->StyleSet()->ResolveNonInheritingAnonymousBoxStyle(
+ PseudoStyleType::columnSpanWrapper);
+ nsBlockFrame* columnSpanWrapper =
+ NS_NewBlockFrame(mPresShell, columnSpanWrapperStyle);
+ InitAndRestoreFrame(aState, content, parentFrame, columnSpanWrapper, false);
+ columnSpanWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR |
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+
+ nsFrameList columnSpanKids =
+ aChildList.Split([](nsIFrame* f) { return !f->IsColumnSpan(); });
+ columnSpanKids.ApplySetParent(columnSpanWrapper);
+ columnSpanWrapper->SetInitialChildList(FrameChildListID::Principal,
+ std::move(columnSpanKids));
+ if (aPositionedFrame) {
+ aState.ReparentAbsoluteItems(columnSpanWrapper);
+ }
+
+ siblings.AppendFrame(nullptr, columnSpanWrapper);
+
+ // Grab the consecutive non-column-span kids, and reparent them into a new
+ // continuation of the last non-column-span wrapper frame.
+ auto* nonColumnSpanWrapper = static_cast<nsContainerFrame*>(
+ CreateContinuingFrame(lastNonColumnSpanWrapper, parentFrame, false));
+ nonColumnSpanWrapper->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR |
+ NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ MOZ_ASSERT(nonColumnSpanWrapper->HasColumnSpanSiblings(),
+ "The bit should propagate to the next continuation!");
+
+ if (aChildList.NotEmpty()) {
+ nsFrameList nonColumnSpanKids =
+ aChildList.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+
+ nonColumnSpanKids.ApplySetParent(nonColumnSpanWrapper);
+ nonColumnSpanWrapper->SetInitialChildList(FrameChildListID::Principal,
+ std::move(nonColumnSpanKids));
+ if (aPositionedFrame) {
+ aState.ReparentAbsoluteItems(nonColumnSpanWrapper);
+ }
+ if (isInitialBlockFloatCB) {
+ aState.ReparentFloats(nonColumnSpanWrapper);
+ }
+ }
+
+ siblings.AppendFrame(nullptr, nonColumnSpanWrapper);
+
+ lastNonColumnSpanWrapper = nonColumnSpanWrapper;
+ } while (aChildList.NotEmpty());
+
+ // Unset the bit because the last non-column-span wrapper has no column-span
+ // siblings.
+ lastNonColumnSpanWrapper->SetHasColumnSpanSiblings(false);
+
+ return siblings;
+}
+
+bool nsCSSFrameConstructor::MaybeRecreateForColumnSpan(
+ nsFrameConstructorState& aState, nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList, nsIFrame* aPrevSibling) {
+ if (!aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ return false;
+ }
+
+ if (aFrameList.IsEmpty()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!IsFramePartOfIBSplit(aParentFrame),
+ "We should have wiped aParentFrame in WipeContainingBlock if it's "
+ "part of IB split!");
+
+ nsIFrame* nextSibling = ::GetInsertNextSibling(aParentFrame, aPrevSibling);
+ if (!nextSibling && IsLastContinuationForColumnContent(aParentFrame)) {
+ // We are appending a list of frames to the last continuation of a
+ // ::-moz-column-content. This is the case where we can fix the frame tree
+ // instead of reframing the containing block. Return false and let
+ // AppendFramesToParent() deal with this.
+ return false;
+ }
+
+ auto HasColumnSpan = [](const nsFrameList& aList) {
+ for (nsIFrame* f : aList) {
+ if (f->IsColumnSpan()) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ if (HasColumnSpan(aFrameList)) {
+ // If any frame in the frame list has "column-span:all" style, i.e. a
+ // -moz-column-span-wrapper frame, we need to reframe the multi-column
+ // containing block.
+ //
+ // We can only be here if none of the new inserted nsIContent* nodes (via
+ // ContentAppended or ContentRangeInserted) have column-span:all style, yet
+ // some of them have column-span:all descendants. Sadly, there's no way to
+ // detect this by checking FrameConstructionItems in WipeContainingBlock().
+ // Otherwise, we would have already wiped the multi-column containing block.
+ PROFILER_MARKER("Reframe multi-column after constructing frame list",
+ LAYOUT, {}, Tracing, "Layout");
+
+ // aFrameList can contain placeholder frames. In order to destroy their
+ // associated out-of-flow frames properly, we need to manually flush all the
+ // out-of-flow frames in aState to their container frames.
+ aState.ProcessFrameInsertionsForAllLists();
+ DestroyContext context(mPresShell);
+ aFrameList.DestroyFrames(context);
+ RecreateFramesForContent(
+ GetMultiColumnContainingBlockFor(aParentFrame)->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+
+ return false;
+}
+
+nsIFrame* nsCSSFrameConstructor::ConstructInline(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList) {
+ // If an inline frame has non-inline kids, then we chop up the child list
+ // into runs of blocks and runs of inlines, create anonymous block frames to
+ // contain the runs of blocks, inline frames with our style for the runs of
+ // inlines, and put all these frames, in order, into aFrameList.
+ //
+ // When there are column-span blocks in a run of blocks, instead of creating
+ // an anonymous block to wrap them, we create multiple anonymous blocks,
+ // wrapping runs of non-column-spans and runs of column-spans.
+ //
+ // We return the the first one. The whole setup is called an {ib}
+ // split; in what follows "frames in the split" refers to the anonymous blocks
+ // and inlines that contain our children.
+ //
+ // {ib} splits maintain the following invariants:
+ // 1) All frames in the split have the NS_FRAME_PART_OF_IBSPLIT bit
+ // set.
+ //
+ // 2) Each frame in the split has the nsIFrame::IBSplitSibling
+ // property pointing to the next frame in the split, except for the last
+ // one, which does not have it set.
+ //
+ // 3) Each frame in the split has the nsIFrame::IBSplitPrevSibling
+ // property pointing to the previous frame in the split, except for the
+ // first one, which does not have it set.
+ //
+ // 4) The first and last frame in the split are always inlines.
+ //
+ // 5) The frames wrapping runs of non-column-spans are linked together as
+ // continuations. The frames wrapping runs of column-spans are *not*
+ // linked with each other nor with other non-column-span wrappers.
+ //
+ // 6) The first and last frame in the chains of blocks are always wrapping
+ // non-column-spans. Both of them are created even if they're empty.
+ //
+ // An invariant that is NOT maintained is that the wrappers are actually
+ // linked via GetNextSibling linkage. A simple example is an inline
+ // containing an inline that contains a block. The three parts of the inner
+ // inline end up with three different parents.
+ //
+ // For example, this HTML:
+ // <span>
+ // <div>a</div>
+ // <span>
+ // b
+ // <div>c</div>
+ // </span>
+ // d
+ // <div>e</div>
+ // f
+ // </span>
+ // Gives the following frame tree:
+ //
+ // Inline (outer span)
+ // Block (anonymous, outer span)
+ // Block (div)
+ // Text("a")
+ // Inline (outer span)
+ // Inline (inner span)
+ // Text("b")
+ // Block (anonymous, outer span)
+ // Block (anonymous, inner span)
+ // Block (div)
+ // Text("c")
+ // Inline (outer span)
+ // Inline (inner span)
+ // Text("d")
+ // Block (anonymous, outer span)
+ // Block (div)
+ // Text("e")
+ // Inline (outer span)
+ // Text("f")
+
+ nsIContent* const content = aItem.mContent;
+ ComputedStyle* const computedStyle = aItem.mComputedStyle;
+
+ nsInlineFrame* newFrame = NS_NewInlineFrame(mPresShell, computedStyle);
+
+ // Initialize the frame
+ InitAndRestoreFrame(aState, content, aParentFrame, newFrame);
+
+ // definition cannot be inside next block because the object's destructor is
+ // significant. this is part of the fix for bug 42372
+ nsFrameConstructorSaveState absoluteSaveState;
+
+ bool isAbsPosCB = newFrame->IsAbsPosContainingBlock();
+ newFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (isAbsPosCB) {
+ // Relatively positioned frames becomes a container for child
+ // frames that are positioned
+ aState.PushAbsoluteContainingBlock(newFrame, newFrame, absoluteSaveState);
+ }
+
+ if (aParentFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR) &&
+ !ShouldSuppressColumnSpanDescendants(aParentFrame)) {
+ newFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+ }
+
+ // Process the child content
+ nsFrameList childList;
+ ConstructFramesFromItemList(aState, aItem.mChildItems, newFrame,
+ /* aParentIsWrapperAnonBox = */ false, childList);
+
+ nsIFrame* firstBlock = nullptr;
+ if (!aItem.mIsAllInline) {
+ for (nsIFrame* f : childList) {
+ if (f->IsBlockOutside()) {
+ firstBlock = f;
+ break;
+ }
+ }
+ }
+
+ if (aItem.mIsAllInline || !firstBlock) {
+ // This part is easy. We either already know we have no non-inline kids,
+ // or haven't found any when constructing actual frames (the latter can
+ // happen only if out-of-flows that we thought had no containing block
+ // acquired one when ancestor inline frames and {ib} splits got
+ // constructed). Just put all the kids into the single inline frame and
+ // bail.
+ newFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(childList));
+ aState.AddChild(newFrame, aFrameList, content, aParentFrame);
+ return newFrame;
+ }
+
+ // This inline frame contains several types of children. Therefore this frame
+ // has to be chopped into several pieces, as described above.
+
+ // Grab the first inline's kids
+ nsFrameList firstInlineKids = childList.TakeFramesBefore(firstBlock);
+ newFrame->SetInitialChildList(FrameChildListID::Principal,
+ std::move(firstInlineKids));
+
+ aFrameList.AppendFrame(nullptr, newFrame);
+
+ newFrame->AddStateBits(NS_FRAME_OWNS_ANON_BOXES);
+ CreateIBSiblings(aState, newFrame, isAbsPosCB, childList, aFrameList);
+
+ return newFrame;
+}
+
+void nsCSSFrameConstructor::CreateIBSiblings(nsFrameConstructorState& aState,
+ nsContainerFrame* aInitialInline,
+ bool aIsAbsPosCB,
+ nsFrameList& aChildList,
+ nsFrameList& aSiblings) {
+ MOZ_ASSERT(aIsAbsPosCB == aInitialInline->IsAbsPosContainingBlock());
+
+ nsIContent* content = aInitialInline->GetContent();
+ ComputedStyle* computedStyle = aInitialInline->Style();
+ nsContainerFrame* parentFrame = aInitialInline->GetParent();
+
+ // Resolve the right style for our anonymous blocks.
+ //
+ // The distinction in styles is needed because of CSS 2.1, section
+ // 9.2.1.1, which says:
+ //
+ // When such an inline box is affected by relative positioning, any
+ // resulting translation also affects the block-level box contained
+ // in the inline box.
+ RefPtr<ComputedStyle> blockSC =
+ mPresShell->StyleSet()->ResolveInheritingAnonymousBoxStyle(
+ PseudoStyleType::mozBlockInsideInlineWrapper, computedStyle);
+
+ nsContainerFrame* lastNewInline =
+ static_cast<nsContainerFrame*>(aInitialInline->FirstContinuation());
+ do {
+ // On entry to this loop aChildList is not empty and the first frame in it
+ // is block-level.
+ MOZ_ASSERT(aChildList.NotEmpty(), "Should have child items");
+ MOZ_ASSERT(aChildList.FirstChild()->IsBlockOutside(),
+ "Must have list starting with block");
+
+ // The initial run of blocks belongs to an anonymous block that we create
+ // right now. The anonymous block will be the parent of these block
+ // children of the inline.
+ nsBlockFrame* blockFrame = NS_NewBlockFrame(mPresShell, blockSC);
+ InitAndRestoreFrame(aState, content, parentFrame, blockFrame, false);
+ if (aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ blockFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+ }
+
+ // Find the first non-block child which defines the end of our block kids
+ // and the start of our next inline's kids
+ nsFrameList blockKids =
+ aChildList.Split([](nsIFrame* f) { return !f->IsBlockOutside(); });
+
+ if (!aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ MoveChildrenTo(aInitialInline, blockFrame, blockKids);
+
+ SetFrameIsIBSplit(lastNewInline, blockFrame);
+ aSiblings.AppendFrame(nullptr, blockFrame);
+ } else {
+ // Extract any initial non-column-span frames, and put them in
+ // blockFrame's child list.
+ nsFrameList initialNonColumnSpanKids =
+ blockKids.Split([](nsIFrame* f) { return f->IsColumnSpan(); });
+ MoveChildrenTo(aInitialInline, blockFrame, initialNonColumnSpanKids);
+
+ SetFrameIsIBSplit(lastNewInline, blockFrame);
+ aSiblings.AppendFrame(nullptr, blockFrame);
+
+ if (blockKids.NotEmpty()) {
+ // Although SetFrameIsIBSplit() will add NS_FRAME_PART_OF_IBSPLIT for
+ // blockFrame later, we manually add the bit earlier here to make all
+ // the continuations of blockFrame created in
+ // CreateColumnSpanSiblings(), i.e. non-column-span wrappers, have the
+ // bit via nsIFrame::Init().
+ blockFrame->AddStateBits(NS_FRAME_PART_OF_IBSPLIT);
+
+ nsFrameList columnSpanSiblings =
+ CreateColumnSpanSiblings(aState, blockFrame, blockKids,
+ aIsAbsPosCB ? aInitialInline : nullptr);
+ aSiblings.AppendFrames(nullptr, std::move(columnSpanSiblings));
+ }
+ }
+
+ // Now grab the initial inlines in aChildList and put them into an inline
+ // frame.
+ nsInlineFrame* inlineFrame = NS_NewInlineFrame(mPresShell, computedStyle);
+ InitAndRestoreFrame(aState, content, parentFrame, inlineFrame, false);
+ inlineFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
+ if (aInitialInline->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ inlineFrame->AddStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR);
+ }
+
+ if (aIsAbsPosCB) {
+ inlineFrame->MarkAsAbsoluteContainingBlock();
+ }
+
+ if (aChildList.NotEmpty()) {
+ nsFrameList inlineKids =
+ aChildList.Split([](nsIFrame* f) { return f->IsBlockOutside(); });
+ MoveChildrenTo(aInitialInline, inlineFrame, inlineKids);
+ }
+
+ SetFrameIsIBSplit(blockFrame, inlineFrame);
+ aSiblings.AppendFrame(nullptr, inlineFrame);
+ lastNewInline = inlineFrame;
+ } while (aChildList.NotEmpty());
+
+ SetFrameIsIBSplit(lastNewInline, nullptr);
+}
+
+void nsCSSFrameConstructor::BuildInlineChildItems(
+ nsFrameConstructorState& aState, FrameConstructionItem& aParentItem,
+ bool aItemIsWithinSVGText, bool aItemAllowsTextPathChild) {
+ ComputedStyle* const parentComputedStyle = aParentItem.mComputedStyle;
+ nsIContent* const parentContent = aParentItem.mContent;
+
+ if (!aItemIsWithinSVGText) {
+ if (parentComputedStyle->StyleDisplay()->IsListItem()) {
+ CreateGeneratedContentItem(aState, nullptr, *parentContent->AsElement(),
+ *parentComputedStyle, PseudoStyleType::marker,
+ aParentItem.mChildItems);
+ }
+ // Probe for generated content before
+ CreateGeneratedContentItem(aState, nullptr, *parentContent->AsElement(),
+ *parentComputedStyle, PseudoStyleType::before,
+ aParentItem.mChildItems);
+ }
+
+ ItemFlags flags;
+ if (aItemIsWithinSVGText) {
+ flags += ItemFlag::IsWithinSVGText;
+ }
+ if (aItemAllowsTextPathChild &&
+ aParentItem.mContent->IsSVGElement(nsGkAtoms::a)) {
+ flags += ItemFlag::AllowTextPathChild;
+ }
+
+ FlattenedChildIterator iter(parentContent);
+ for (nsIContent* content = iter.GetNextChild(); content;
+ content = iter.GetNextChild()) {
+ AddFrameConstructionItems(aState, content, iter.ShadowDOMInvolved(),
+ *parentComputedStyle, InsertionPoint(),
+ aParentItem.mChildItems, flags);
+ }
+
+ if (!aItemIsWithinSVGText) {
+ // Probe for generated content after
+ CreateGeneratedContentItem(aState, nullptr, *parentContent->AsElement(),
+ *parentComputedStyle, PseudoStyleType::after,
+ aParentItem.mChildItems);
+ }
+
+ aParentItem.mIsAllInline = aParentItem.mChildItems.AreAllItemsInline();
+}
+
+// return whether it's ok to append (in the AppendFrames sense) to
+// aParentFrame if our nextSibling is aNextSibling. aParentFrame must
+// be an ib-split inline.
+static bool IsSafeToAppendToIBSplitInline(nsIFrame* aParentFrame,
+ nsIFrame* aNextSibling) {
+ MOZ_ASSERT(IsInlineFrame(aParentFrame), "Must have an inline parent here");
+
+ do {
+ NS_ASSERTION(IsFramePartOfIBSplit(aParentFrame),
+ "How is this not part of an ib-split?");
+ if (aNextSibling || aParentFrame->GetNextContinuation() ||
+ GetIBSplitSibling(aParentFrame)) {
+ return false;
+ }
+
+ aNextSibling = aParentFrame->GetNextSibling();
+ aParentFrame = aParentFrame->GetParent();
+ } while (IsInlineFrame(aParentFrame));
+
+ return true;
+}
+
+bool nsCSSFrameConstructor::WipeInsertionParent(nsContainerFrame* aFrame) {
+#define TRACE(reason) \
+ PROFILER_MARKER("WipeInsertionParent: " reason, LAYOUT, {}, Tracing, \
+ "Layout");
+
+ const LayoutFrameType frameType = aFrame->Type();
+
+ // FIXME(emilio): This looks terribly inefficient if you insert elements deep
+ // in a MathML subtree.
+ if (aFrame->IsMathMLFrame()) {
+ TRACE("MathML");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // A ruby-related frame that's getting new children.
+ // The situation for ruby is complex, especially when interacting with
+ // spaces. It contains these two special cases apart from tables:
+ // 1) There are effectively three types of white spaces in ruby frames
+ // we handle differently: leading/tailing/inter-level space,
+ // inter-base/inter-annotation space, and inter-segment space.
+ // These three types of spaces can be converted to each other when
+ // their sibling changes.
+ // 2) The first effective child of a ruby frame must always be a ruby
+ // base container. It should be created or destroyed accordingly.
+ if (IsRubyPseudo(aFrame) || frameType == LayoutFrameType::Ruby ||
+ RubyUtils::IsRubyContainerBox(frameType)) {
+ // We want to optimize it better, and avoid reframing as much as
+ // possible. But given the cases above, and the fact that a ruby
+ // usually won't be very large, it should be fine to reframe it.
+ TRACE("Ruby");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // Reframe the multi-column container whenever elements insert/append
+ // into it because we need to reconstruct column-span split.
+ if (aFrame->IsColumnSetWrapperFrame()) {
+ TRACE("Multi-column");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ return false;
+
+#undef TRACE
+}
+
+bool nsCSSFrameConstructor::WipeContainingBlock(
+ nsFrameConstructorState& aState, nsIFrame* aContainingBlock,
+ nsIFrame* aFrame, FrameConstructionItemList& aItems, bool aIsAppend,
+ nsIFrame* aPrevSibling) {
+#define TRACE(reason) \
+ PROFILER_MARKER("WipeContainingBlock: " reason, LAYOUT, {}, Tracing, \
+ "Layout");
+
+ if (aItems.IsEmpty()) {
+ return false;
+ }
+
+ // Before we go and append the frames, we must check for several
+ // special situations.
+
+ if (aFrame->GetContent() == mDocument->GetRootElement()) {
+ // Situation #1 is when we insert content that becomes the canonical body
+ // element, and its used WritingMode is different from the root element's
+ // used WritingMode.
+ // We need to reframe the root element so that the root element's frames has
+ // the correct writing-mode propagated from body element. (See
+ // nsCSSFrameConstructor::ConstructDocElementFrame.)
+ //
+ // Bug 1594297: When inserting a new <body>, we may need to reframe the old
+ // <body> which has a "overflow" value other than simple "visible". But it's
+ // tricky, see bug 1593752.
+ nsIContent* bodyElement = mDocument->GetBodyElement();
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ const WritingMode bodyWM(iter.item().mComputedStyle);
+ if (iter.item().mContent == bodyElement &&
+ bodyWM != aFrame->GetWritingMode()) {
+ TRACE("Root");
+ RecreateFramesForContent(mDocument->GetRootElement(),
+ InsertionKind::Async);
+ return true;
+ }
+ }
+ }
+
+ nsIFrame* nextSibling = ::GetInsertNextSibling(aFrame, aPrevSibling);
+
+ // Situation #2 is a flex / grid container frame into which we're inserting
+ // new inline non-replaced children, adjacent to an existing anonymous flex or
+ // grid item.
+ if (aFrame->IsFlexOrGridContainer()) {
+ FCItemIterator iter(aItems);
+
+ // Check if we're adding to-be-wrapped content right *after* an existing
+ // anonymous flex or grid item (which would need to absorb this content).
+ const bool isLegacyWebKitBox = IsFlexContainerForLegacyWebKitBox(aFrame);
+ if (aPrevSibling && IsAnonymousItem(aPrevSibling) &&
+ iter.item().NeedsAnonFlexOrGridItem(aState, isLegacyWebKitBox)) {
+ TRACE("Inserting inline after anon flex or grid item");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+
+ // Check if we're adding to-be-wrapped content right *before* an existing
+ // anonymous flex or grid item (which would need to absorb this content).
+ if (nextSibling && IsAnonymousItem(nextSibling)) {
+ // Jump to the last entry in the list
+ iter.SetToEnd();
+ iter.Prev();
+ if (iter.item().NeedsAnonFlexOrGridItem(aState, isLegacyWebKitBox)) {
+ TRACE("Inserting inline before anon flex or grid item");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+ }
+ }
+
+ // Situation #3 is an anonymous flex or grid item that's getting new children
+ // who don't want to be wrapped.
+ if (IsAnonymousItem(aFrame)) {
+ AssertAnonymousFlexOrGridItemParent(aFrame, aFrame->GetParent());
+
+ // We need to push a null float containing block to be sure that
+ // "NeedsAnonFlexOrGridItem" will know we're not honoring floats for this
+ // inserted content. (In particular, this is necessary in order for
+ // its "GetGeometricParent" call to return the correct result.)
+ // We're not honoring floats on this content because it has the
+ // _flex/grid container_ as its parent in the content tree.
+ nsFrameConstructorSaveState floatSaveState;
+ aState.PushFloatContainingBlock(nullptr, floatSaveState);
+
+ FCItemIterator iter(aItems);
+ // Skip over things that _do_ need an anonymous flex item, because
+ // they're perfectly happy to go here -- they won't cause a reframe.
+ nsIFrame* containerFrame = aFrame->GetParent();
+ const bool isLegacyWebKitBox =
+ IsFlexContainerForLegacyWebKitBox(containerFrame);
+ if (!iter.SkipItemsThatNeedAnonFlexOrGridItem(aState, isLegacyWebKitBox)) {
+ // We hit something that _doesn't_ need an anonymous flex item!
+ // Rebuild the flex container to bust it out.
+ TRACE("Inserting non-inlines inside anon flex or grid item");
+ RecreateFramesForContent(containerFrame->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+
+ // If we get here, then everything in |aItems| needs to be wrapped in
+ // an anonymous flex or grid item. That's where it's already going - good!
+ }
+
+ // Situation #4 is a case when table pseudo-frames don't work out right
+ ParentType parentType = GetParentType(aFrame);
+ // If all the kids want a parent of the type that aFrame is, then we're all
+ // set to go. Indeed, there won't be any table pseudo-frames created between
+ // aFrame and the kids, so those won't need to be merged with any table
+ // pseudo-frames that might already be kids of aFrame. If aFrame itself is a
+ // table pseudo-frame, then all the kids in this list would have wanted a
+ // frame of that type wrapping them anyway, so putting them inside it is ok.
+ if (!aItems.AllWantParentType(parentType)) {
+ // Don't give up yet. If parentType is not eTypeBlock and the parent is
+ // not a generated content frame, then try filtering whitespace out of the
+ // list.
+ if (parentType != eTypeBlock && !aFrame->IsGeneratedContentFrame()) {
+ // For leading whitespace followed by a kid that wants our parent type,
+ // there are four cases:
+ // 1) We have a previous sibling which is not a table pseudo. That means
+ // that previous sibling wanted a (non-block) parent of the type we're
+ // looking at. Then the whitespace comes between two table-internal
+ // elements, so should be collapsed out.
+ // 2) We have a previous sibling which is a table pseudo. It might have
+ // kids who want this whitespace, so we need to reframe.
+ // 3) We have no previous sibling and our parent frame is not a table
+ // pseudo. That means that we'll be at the beginning of our actual
+ // non-block-type parent, and the whitespace is OK to collapse out.
+ // If something is ever inserted before us, it'll find our own parent
+ // as its parent and if it's something that would care about the
+ // whitespace it'll want a block parent, so it'll trigger a reframe at
+ // that point.
+ // 4) We have no previous sibling and our parent frame is a table pseudo.
+ // Need to reframe.
+ // All that is predicated on finding the correct previous sibling. We
+ // might have to walk backwards along continuations from aFrame to do so.
+ //
+ // It's always OK to drop whitespace between any two items that want a
+ // parent of type parentType.
+ //
+ // For trailing whitespace preceded by a kid that wants our parent type,
+ // there are four cases:
+ // 1) We have a next sibling which is not a table pseudo. That means
+ // that next sibling wanted a (non-block) parent of the type we're
+ // looking at. Then the whitespace comes between two table-internal
+ // elements, so should be collapsed out.
+ // 2) We have a next sibling which is a table pseudo. It might have
+ // kids who want this whitespace, so we need to reframe.
+ // 3) We have no next sibling and our parent frame is not a table
+ // pseudo. That means that we'll be at the end of our actual
+ // non-block-type parent, and the whitespace is OK to collapse out.
+ // If something is ever inserted after us, it'll find our own parent
+ // as its parent and if it's something that would care about the
+ // whitespace it'll want a block parent, so it'll trigger a reframe at
+ // that point.
+ // 4) We have no next sibling and our parent frame is a table pseudo.
+ // Need to reframe.
+ // All that is predicated on finding the correct next sibling. We might
+ // have to walk forward along continuations from aFrame to do so. That
+ // said, in the case when nextSibling is null at this point and aIsAppend
+ // is true, we know we're in case 3. Furthermore, in that case we don't
+ // even have to worry about the table pseudo situation; we know our
+ // parent is not a table pseudo there.
+ FCItemIterator iter(aItems);
+ FCItemIterator start(iter);
+ do {
+ if (iter.SkipItemsWantingParentType(parentType)) {
+ break;
+ }
+
+ // iter points to an item that wants a different parent. If it's not
+ // whitespace, we're done; no more point scanning the list.
+ if (!iter.item().IsWhitespace(aState)) {
+ break;
+ }
+
+ if (iter == start) {
+ // Leading whitespace. How to handle this depends on our
+ // previous sibling and aFrame. See the long comment above.
+ nsIFrame* prevSibling = aPrevSibling;
+ if (!prevSibling) {
+ // Try to find one after all
+ nsIFrame* parentPrevCont = aFrame->GetPrevContinuation();
+ while (parentPrevCont) {
+ prevSibling = parentPrevCont->PrincipalChildList().LastChild();
+ if (prevSibling) {
+ break;
+ }
+ parentPrevCont = parentPrevCont->GetPrevContinuation();
+ }
+ };
+ if (prevSibling) {
+ if (IsTablePseudo(prevSibling)) {
+ // need to reframe
+ break;
+ }
+ } else if (IsTablePseudo(aFrame)) {
+ // need to reframe
+ break;
+ }
+ }
+
+ FCItemIterator spaceEndIter(iter);
+ // Advance spaceEndIter past any whitespace
+ bool trailingSpaces = spaceEndIter.SkipWhitespace(aState);
+
+ bool okToDrop;
+ if (trailingSpaces) {
+ // Trailing whitespace. How to handle this depeds on aIsAppend, our
+ // next sibling and aFrame. See the long comment above.
+ okToDrop = aIsAppend && !nextSibling;
+ if (!okToDrop) {
+ if (!nextSibling) {
+ // Try to find one after all
+ nsIFrame* parentNextCont = aFrame->GetNextContinuation();
+ while (parentNextCont) {
+ nextSibling = parentNextCont->PrincipalChildList().FirstChild();
+ if (nextSibling) {
+ break;
+ }
+ parentNextCont = parentNextCont->GetNextContinuation();
+ }
+ }
+
+ okToDrop = (nextSibling && !IsTablePseudo(nextSibling)) ||
+ (!nextSibling && !IsTablePseudo(aFrame));
+ }
+#ifdef DEBUG
+ else {
+ NS_ASSERTION(!IsTablePseudo(aFrame), "How did that happen?");
+ }
+#endif
+ } else {
+ okToDrop = (spaceEndIter.item().DesiredParentType() == parentType);
+ }
+
+ if (okToDrop) {
+ iter.DeleteItemsTo(this, spaceEndIter);
+ } else {
+ // We're done: we don't want to drop the whitespace, and it has the
+ // wrong parent type.
+ break;
+ }
+
+ // Now loop, since |iter| points to item right after the whitespace we
+ // removed.
+ } while (!iter.IsDone());
+ }
+
+ // We might be able to figure out some sort of optimizations here, but they
+ // would have to depend on having a correct aPrevSibling and a correct next
+ // sibling. For example, we can probably avoid reframing if none of
+ // aFrame, aPrevSibling, and next sibling are table pseudo-frames. But it
+ // doesn't seem worth it to worry about that for now, especially since we
+ // in fact do not have a reliable aPrevSibling, nor any next sibling, in
+ // this method.
+
+ // aItems might have changed, so recheck the parent type thing. In fact,
+ // it might be empty, so recheck that too.
+ if (aItems.IsEmpty()) {
+ return false;
+ }
+
+ if (!aItems.AllWantParentType(parentType)) {
+ // Reframing aFrame->GetContent() is good enough, since the content of
+ // table pseudo-frames is the ancestor content.
+ TRACE("Pseudo-frames going wrong");
+ RecreateFramesForContent(aFrame->GetContent(), InsertionKind::Async);
+ return true;
+ }
+ }
+
+ // Situation #5 is a frame in multicol subtree that's getting new children.
+ if (aFrame->HasAnyStateBits(NS_FRAME_HAS_MULTI_COLUMN_ANCESTOR)) {
+ bool anyColumnSpanItems = false;
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ if (iter.item().mComputedStyle->StyleColumn()->IsColumnSpanStyle()) {
+ anyColumnSpanItems = true;
+ break;
+ }
+ }
+
+ bool needsReframe =
+ // 1. Insert / append any column-span children.
+ anyColumnSpanItems ||
+ // 2. GetInsertionPrevSibling() modifies insertion parent. If the prev
+ // sibling is a column-span, aFrame ends up being the
+ // column-span-wrapper.
+ aFrame->Style()->GetPseudoType() ==
+ PseudoStyleType::columnSpanWrapper ||
+ // 3. Append into {ib} split container. There might be room for
+ // optimization, but let's reframe for correctness...
+ IsFramePartOfIBSplit(aFrame);
+
+ if (needsReframe) {
+ TRACE("Multi-column");
+ RecreateFramesForContent(
+ GetMultiColumnContainingBlockFor(aFrame)->GetContent(),
+ InsertionKind::Async);
+ return true;
+ }
+
+ // If we get here, then we need further check for {ib} split to decide
+ // whether to reframe. For example, appending a block into an empty inline
+ // that is not part of an {ib} split, but should become an {ib} split.
+ }
+
+ // A <fieldset> may need to pick up a new rendered legend from aItems.
+ // We currently can't handle this case without recreating frames for
+ // the fieldset.
+ // XXXmats we should be able to optimize this when the fieldset doesn't
+ // currently have a rendered legend. ContentRangeInserted needs to be fixed
+ // to use the inner frame as the content insertion frame in that case.
+ if (const auto* fieldset = GetFieldSetFrameFor(aFrame)) {
+ // Check if any item is eligible to be a rendered legend.
+ for (FCItemIterator iter(aItems); !iter.IsDone(); iter.Next()) {
+ const auto& item = iter.item();
+ if (!item.mContent->IsHTMLElement(nsGkAtoms::legend)) {
+ continue;
+ }
+ const auto* display = item.mComputedStyle->StyleDisplay();
+ if (display->IsFloatingStyle() ||
+ display->IsAbsolutelyPositionedStyle()) {
+ continue;
+ }
+ TRACE("Fieldset with rendered legend");
+ RecreateFramesForContent(fieldset->GetContent(), InsertionKind::Async);
+ return true;
+ }
+ }
+
+ // Now we have several cases involving {ib} splits. Put them all in a
+ // do/while with breaks to take us to the "go and reconstruct" code.
+ do {
+ if (IsInlineFrame(aFrame)) {
+ if (aItems.AreAllItemsInline()) {
+ // We can just put the kids in.
+ return false;
+ }
+
+ if (!IsFramePartOfIBSplit(aFrame)) {
+ // Need to go ahead and reconstruct.
+ break;
+ }
+
+ // Now we're adding kids including some blocks to an inline part of an
+ // {ib} split. If we plan to call AppendFrames, and don't have a next
+ // sibling for the new frames, and our parent is the last continuation of
+ // the last part of the {ib} split, and the same is true of all our
+ // ancestor inlines (they have no following continuations and they're the
+ // last part of their {ib} splits and we'd be adding to the end for all
+ // of them), then AppendFrames will handle things for us. Bail out in
+ // that case.
+ if (aIsAppend && IsSafeToAppendToIBSplitInline(aFrame, nextSibling)) {
+ return false;
+ }
+
+ // Need to reconstruct.
+ break;
+ }
+
+ // Now we know we have a block parent. If it's not part of an
+ // ib-split, we're all set.
+ if (!IsFramePartOfIBSplit(aFrame)) {
+ return false;
+ }
+
+ // We're adding some kids to a block part of an {ib} split. If all the
+ // kids are blocks, we don't need to reconstruct.
+ if (aItems.AreAllItemsBlock()) {
+ return false;
+ }
+
+ // We might have some inline kids for this block. Just fall out of the
+ // loop and reconstruct.
+ } while (0);
+
+ // If we don't have a containing block, start with aFrame and look for one.
+ if (!aContainingBlock) {
+ aContainingBlock = aFrame;
+ }
+
+ // To find the right block to reframe, just walk up the tree until we find a
+ // frame that is:
+ // 1) Not part of an IB split
+ // 2) Not a pseudo-frame
+ // 3) Not an inline frame
+ // We're guaranteed to find one, since ComputedStyle::ApplyStyleFixups
+ // enforces that the root is display:none, display:table, or display:block.
+ // Note that walking up "too far" is OK in terms of correctness, even if it
+ // might be a little inefficient. This is why we walk out of all
+ // pseudo-frames -- telling which ones are or are not OK to walk out of is
+ // too hard (and I suspect that we do in fact need to walk out of all of
+ // them).
+ while (IsFramePartOfIBSplit(aContainingBlock) ||
+ aContainingBlock->IsInlineOutside() ||
+ aContainingBlock->Style()->IsPseudoOrAnonBox()) {
+ aContainingBlock = aContainingBlock->GetParent();
+ NS_ASSERTION(aContainingBlock,
+ "Must have non-inline, non-ib-split, non-pseudo frame as "
+ "root (or child of root, for a table root)!");
+ }
+
+ // Tell parent of the containing block to reformulate the
+ // entire block. This is painful and definitely not optimal
+ // but it will *always* get the right answer.
+
+ nsIContent* blockContent = aContainingBlock->GetContent();
+ TRACE("IB splits");
+ RecreateFramesForContent(blockContent, InsertionKind::Async);
+ return true;
+#undef TRACE
+}
+
+void nsCSSFrameConstructor::ReframeContainingBlock(nsIFrame* aFrame) {
+ // XXXbz how exactly would we get here while isReflowing anyway? Should this
+ // whole test be ifdef DEBUG?
+ if (mPresShell->IsReflowLocked()) {
+ // don't ReframeContainingBlock, this will result in a crash
+ // if we remove a tree that's in reflow - see bug 121368 for testcase
+ NS_ERROR(
+ "Atemptted to nsCSSFrameConstructor::ReframeContainingBlock during a "
+ "Reflow!!!");
+ return;
+ }
+
+ // Get the first "normal" ancestor of the target frame.
+ nsIFrame* containingBlock = GetIBContainingBlockFor(aFrame);
+ if (containingBlock) {
+ // From here we look for the containing block in case the target
+ // frame is already a block (which can happen when an inline frame
+ // wraps some of its content in an anonymous block; see
+ // ConstructInline)
+
+ // NOTE: We used to get the FloatContainingBlock here, but it was often
+ // wrong. GetIBContainingBlock works much better and provides the correct
+ // container in all cases so GetFloatContainingBlock(aFrame) has been
+ // removed
+
+ // And get the containingBlock's content
+ if (nsIContent* blockContent = containingBlock->GetContent()) {
+#ifdef DEBUG
+ if (gNoisyContentUpdates) {
+ printf(" ==> blockContent=%p\n", blockContent);
+ }
+#endif
+ RecreateFramesForContent(blockContent, InsertionKind::Async);
+ return;
+ }
+ }
+
+ // If we get here, we're screwed!
+ RecreateFramesForContent(mPresShell->GetDocument()->GetRootElement(),
+ InsertionKind::Async);
+}
+
+//////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItem methods //
+//////////////////////////////////////////////////////////
+bool nsCSSFrameConstructor::FrameConstructionItem::IsWhitespace(
+ nsFrameConstructorState& aState) const {
+ MOZ_ASSERT(aState.mCreatingExtraFrames || !mContent->GetPrimaryFrame(),
+ "How did that happen?");
+ if (!mIsText) {
+ return false;
+ }
+ mContent->SetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE |
+ NS_REFRAME_IF_WHITESPACE);
+ return mContent->TextIsOnlyWhitespace();
+}
+
+//////////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItemList methods //
+//////////////////////////////////////////////////////////////
+void nsCSSFrameConstructor::FrameConstructionItemList::AdjustCountsForItem(
+ FrameConstructionItem* aItem, int32_t aDelta) {
+ MOZ_ASSERT(aDelta == 1 || aDelta == -1, "Unexpected delta");
+ mItemCount += aDelta;
+ if (aItem->mIsAllInline) {
+ mInlineCount += aDelta;
+ }
+ if (aItem->mIsBlock) {
+ mBlockCount += aDelta;
+ }
+ mDesiredParentCounts[aItem->DesiredParentType()] += aDelta;
+}
+
+////////////////////////////////////////////////////////////////////////
+// nsCSSFrameConstructor::FrameConstructionItemList::Iterator methods //
+////////////////////////////////////////////////////////////////////////
+inline bool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ SkipItemsWantingParentType(ParentType aParentType) {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ while (item().DesiredParentType() == aParentType) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ SkipItemsNotWantingParentType(ParentType aParentType) {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ while (item().DesiredParentType() != aParentType) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// Note: we implement -webkit-{inline-}box using nsFlexContainerFrame, but we
+// use different rules for what gets wrapped in an anonymous flex item.
+bool nsCSSFrameConstructor::FrameConstructionItem::NeedsAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState, bool aIsLegacyWebKitBox) {
+ if (mFCData->mBits & FCDATA_IS_LINE_PARTICIPANT) {
+ // This will be an inline non-replaced box.
+ return true;
+ }
+
+ if (aIsLegacyWebKitBox) {
+ if (mComputedStyle->StyleDisplay()->IsInlineOutsideStyle()) {
+ // In an emulated legacy box, all inline-level content gets wrapped in an
+ // anonymous flex item.
+ return true;
+ }
+ if (mIsPopup ||
+ (!(mFCData->mBits & FCDATA_DISALLOW_OUT_OF_FLOW) &&
+ aState.GetGeometricParent(*mComputedStyle->StyleDisplay(), nullptr))) {
+ // We're abspos or fixedpos (or a XUL popup), which means we'll spawn a
+ // placeholder which (because our container is an emulated legacy box)
+ // we'll need to wrap in an anonymous flex item. So, we just treat
+ // _this_ frame as if _it_ needs to be wrapped in an anonymous flex item,
+ // and then when we spawn the placeholder, it'll end up in the right
+ // spot.
+ return true;
+ }
+ }
+
+ return false;
+}
+
+inline bool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ SkipItemsThatNeedAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+ bool aIsLegacyWebKitBox) {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ while (item().NeedsAnonFlexOrGridItem(aState, aIsLegacyWebKitBox)) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ SkipItemsThatDontNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState, bool aIsLegacyWebKitBox) {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ while (!(item().NeedsAnonFlexOrGridItem(aState, aIsLegacyWebKitBox))) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ SkipItemsNotWantingRubyParent() {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ while (!IsRubyParentType(item().DesiredParentType())) {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+inline bool
+nsCSSFrameConstructor::FrameConstructionItemList::Iterator::SkipWhitespace(
+ nsFrameConstructorState& aState) {
+ MOZ_ASSERT(!IsDone(), "Shouldn't be done yet");
+ MOZ_ASSERT(item().IsWhitespace(aState), "Not pointing to whitespace?");
+ do {
+ Next();
+ if (IsDone()) {
+ return true;
+ }
+ } while (item().IsWhitespace(aState));
+
+ return false;
+}
+
+void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ AppendItemToList(FrameConstructionItemList& aTargetList) {
+ NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
+ MOZ_ASSERT(!IsDone(), "should not be done");
+
+ FrameConstructionItem* item = mCurrent;
+ Next();
+ item->remove();
+ aTargetList.mItems.insertBack(item);
+
+ mList.AdjustCountsForItem(item, -1);
+ aTargetList.AdjustCountsForItem(item, 1);
+}
+
+void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::
+ AppendItemsToList(nsCSSFrameConstructor* aFCtor, const Iterator& aEnd,
+ FrameConstructionItemList& aTargetList) {
+ NS_ASSERTION(&aTargetList != &mList, "Unexpected call");
+ MOZ_ASSERT(&mList == &aEnd.mList, "End iterator for some other list?");
+
+ // We can't just move our guts to the other list if it already has
+ // some information or if we're not moving our entire list.
+ if (!AtStart() || !aEnd.IsDone() || !aTargetList.IsEmpty()) {
+ do {
+ AppendItemToList(aTargetList);
+ } while (*this != aEnd);
+ return;
+ }
+
+ // Move our entire list of items into the empty target list.
+ aTargetList.mItems = std::move(mList.mItems);
+
+ // Copy over the various counters
+ aTargetList.mInlineCount = mList.mInlineCount;
+ aTargetList.mBlockCount = mList.mBlockCount;
+ aTargetList.mItemCount = mList.mItemCount;
+ memcpy(aTargetList.mDesiredParentCounts, mList.mDesiredParentCounts,
+ sizeof(aTargetList.mDesiredParentCounts));
+
+ // reset mList
+ mList.Reset(aFCtor);
+
+ // Point ourselves to aEnd, as advertised
+ SetToEnd();
+ MOZ_ASSERT(*this == aEnd, "How did that happen?");
+}
+
+void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::InsertItem(
+ FrameConstructionItem* aItem) {
+ if (IsDone()) {
+ mList.mItems.insertBack(aItem);
+ } else {
+ // Just insert the item before us. There's no magic here.
+ mCurrent->setPrevious(aItem);
+ }
+ mList.AdjustCountsForItem(aItem, 1);
+
+ MOZ_ASSERT(aItem->getNext() == mCurrent, "How did that happen?");
+}
+
+void nsCSSFrameConstructor::FrameConstructionItemList::Iterator::DeleteItemsTo(
+ nsCSSFrameConstructor* aFCtor, const Iterator& aEnd) {
+ MOZ_ASSERT(&mList == &aEnd.mList, "End iterator for some other list?");
+ MOZ_ASSERT(*this != aEnd, "Shouldn't be at aEnd yet");
+
+ do {
+ NS_ASSERTION(!IsDone(), "Ran off end of list?");
+ FrameConstructionItem* item = mCurrent;
+ Next();
+ item->remove();
+ mList.AdjustCountsForItem(item, -1);
+ item->Delete(aFCtor);
+ } while (*this != aEnd);
+}
+
+void nsCSSFrameConstructor::QuotesDirty() {
+ mQuotesDirty = true;
+ mPresShell->SetNeedLayoutFlush();
+}
+
+void nsCSSFrameConstructor::CountersDirty() {
+ mCountersDirty = true;
+ mPresShell->SetNeedLayoutFlush();
+}
+
+void* nsCSSFrameConstructor::AllocateFCItem() {
+ void* item;
+ if (mFirstFreeFCItem) {
+ item = mFirstFreeFCItem;
+ mFirstFreeFCItem = mFirstFreeFCItem->mNext;
+ } else {
+ item = mFCItemPool.Allocate(sizeof(FrameConstructionItem));
+ }
+ ++mFCItemsInUse;
+ return item;
+}
+
+void nsCSSFrameConstructor::FreeFCItem(FrameConstructionItem* aItem) {
+ MOZ_ASSERT(mFCItemsInUse != 0);
+ if (--mFCItemsInUse == 0) {
+ // The arena is now unused - clear it but retain one chunk.
+ mFirstFreeFCItem = nullptr;
+ mFCItemPool.Clear();
+ } else {
+ // Prepend it to the list of free items.
+ FreeFCItemLink* item = reinterpret_cast<FreeFCItemLink*>(aItem);
+ item->mNext = mFirstFreeFCItem;
+ mFirstFreeFCItem = item;
+ }
+}
+
+void nsCSSFrameConstructor::AddSizeOfIncludingThis(
+ nsWindowSizes& aSizes) const {
+ if (nsIFrame* rootFrame = GetRootFrame()) {
+ rootFrame->AddSizeOfExcludingThisForTree(aSizes);
+ if (RetainedDisplayListBuilder* builder =
+ rootFrame->GetProperty(RetainedDisplayListBuilder::Cached())) {
+ builder->AddSizeOfIncludingThis(aSizes);
+ }
+ }
+
+ // This must be done after measuring from the frame tree, since frame
+ // manager will measure sizes of staled computed values and style
+ // structs, which only make sense after we know what are being used.
+ nsFrameManager::AddSizeOfIncludingThis(aSizes);
+
+ // Measurement of the following members may be added later if DMD finds it
+ // is worthwhile:
+ // - mFCItemPool
+ // - mContainStyleScopeManager
+}
diff --git a/layout/base/nsCSSFrameConstructor.h b/layout/base/nsCSSFrameConstructor.h
new file mode 100644
index 0000000000..1b48fd15dc
--- /dev/null
+++ b/layout/base/nsCSSFrameConstructor.h
@@ -0,0 +1,2159 @@
+/* -*- 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/. */
+
+/*
+ * construction of a frame tree that is nearly isomorphic to the content
+ * tree and updating of that tree in response to dynamic changes
+ */
+
+#ifndef nsCSSFrameConstructor_h___
+#define nsCSSFrameConstructor_h___
+
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ContainStyleScopeManager.h"
+#include "mozilla/FunctionRef.h"
+#include "mozilla/LinkedList.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ScrollStyles.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/PresShell.h"
+
+#include "nsCOMPtr.h"
+#include "nsILayoutHistoryState.h"
+#include "nsIAnonymousContentCreator.h"
+#include "nsFrameManager.h"
+#include "nsIFrame.h"
+
+struct nsStyleDisplay;
+struct nsGenConInitializer;
+
+class nsBlockFrame;
+class nsContainerFrame;
+class nsFirstLineFrame;
+class nsFirstLetterFrame;
+class nsCSSAnonBoxPseudoStaticAtom;
+class nsPageSequenceFrame;
+
+class nsPageContentFrame;
+
+class nsFrameConstructorState;
+
+namespace mozilla {
+
+class ComputedStyle;
+class PresShell;
+class PrintedSheetFrame;
+class RestyleManager;
+
+namespace dom {
+
+class CharacterData;
+class Text;
+class FlattenedChildIterator;
+
+} // namespace dom
+} // namespace mozilla
+
+class nsCSSFrameConstructor final : public nsFrameManager {
+ public:
+ using ComputedStyle = mozilla::ComputedStyle;
+ using PseudoStyleType = mozilla::PseudoStyleType;
+ using PresShell = mozilla::PresShell;
+ using Element = mozilla::dom::Element;
+ using Text = mozilla::dom::Text;
+
+ // FIXME(emilio): Is this really needed?
+ friend class mozilla::RestyleManager;
+
+ nsCSSFrameConstructor(mozilla::dom::Document* aDocument,
+ PresShell* aPresShell);
+ ~nsCSSFrameConstructor() { MOZ_ASSERT(mFCItemsInUse == 0); }
+
+ static void GetAlternateTextFor(const Element&, nsAString& aAltText);
+
+ private:
+ nsCSSFrameConstructor(const nsCSSFrameConstructor& aCopy) = delete;
+ nsCSSFrameConstructor& operator=(const nsCSSFrameConstructor& aCopy) = delete;
+
+ public:
+ /**
+ * Whether insertion should be done synchronously or asynchronously.
+ *
+ * Generally, insertion is synchronous if we're entering frame construction
+ * from restyle processing, and async if we're removing stuff, or need to
+ * reconstruct some ancestor.
+ *
+ * Note that constructing async from frame construction will post a restyle
+ * event, but won't need another whole refresh driver tick to go in. Instead
+ * change hint processing will keep going as long as there are changes in the
+ * queue.
+ */
+ enum class InsertionKind {
+ Sync,
+ Async,
+ };
+
+ mozilla::RestyleManager* RestyleManager() const {
+ return mPresShell->GetPresContext()->RestyleManager();
+ }
+
+ nsIFrame* ConstructRootFrame();
+
+ private:
+ enum Operation { CONTENTAPPEND, CONTENTINSERT };
+
+ // aChild is the child being inserted for inserts, and the first
+ // child being appended for appends.
+ void ConstructLazily(Operation aOperation, nsIContent* aChild);
+
+#ifdef DEBUG
+ void CheckBitsForLazyFrameConstruction(nsIContent* aParent);
+#else
+ void CheckBitsForLazyFrameConstruction(nsIContent*) {}
+#endif
+
+ // Issues a single ContentInserted for each child in the range
+ // [aStartChild, aEndChild).
+ void IssueSingleInsertNofications(nsIContent* aStartChild,
+ nsIContent* aEndChild, InsertionKind);
+
+ /**
+ * Data that represents an insertion point for some child content.
+ */
+ struct InsertionPoint {
+ InsertionPoint() : mParentFrame(nullptr), mContainer(nullptr) {}
+
+ InsertionPoint(nsContainerFrame* aParentFrame, nsIContent* aContainer)
+ : mParentFrame(aParentFrame), mContainer(aContainer) {}
+
+ /**
+ * The parent frame to use if the inserted children needs to create
+ * frame(s). May be null, which signals that we shouldn't try to
+ * create frames for the inserted children; either because there are
+ * no parent frame or because there are multiple insertion points and
+ * we will call IssueSingleInsertNofications for each child instead.
+ * mContainer should not be used when mParentFrame is null.
+ */
+ nsContainerFrame* mParentFrame;
+ /**
+ * The flattened tree parent for the inserted children.
+ * It's undefined if mParentFrame is null.
+ */
+ nsIContent* mContainer;
+
+ /**
+ * Whether it is required to insert children one-by-one instead of as a
+ * range.
+ */
+ bool IsMultiple() const;
+ };
+
+ /**
+ * Checks if the children in the range [aStartChild, aEndChild) can be
+ * inserted/appended to one insertion point together.
+ *
+ * If so, returns that insertion point. If not, returns with
+ * InsertionPoint.mFrame == nullptr and issues single ContentInserted calls
+ * for each child.
+ *
+ * aEndChild = nullptr indicates that we are dealing with an append.
+ */
+ InsertionPoint GetRangeInsertionPoint(nsIContent* aStartChild,
+ nsIContent* aEndChild, InsertionKind);
+
+ // Returns true if parent was recreated due to frameset child, false
+ // otherwise.
+ bool MaybeRecreateForFrameset(nsIFrame* aParentFrame, nsIContent* aStartChild,
+ nsIContent* aEndChild);
+
+ /**
+ * For each child in the aStartChild/aEndChild range, calls
+ * NoteDirtyDescendantsForServo on their flattened tree parents. This is
+ * used when content is inserted into the document and we decide that
+ * we can do lazy frame construction. It handles children being rebound to
+ * different insertion points by calling NoteDirtyDescendantsForServo on each
+ * child's flattened tree parent. Only used when we are styled by Servo.
+ */
+ void LazilyStyleNewChildRange(nsIContent* aStartChild, nsIContent* aEndChild);
+
+ /**
+ * For each child in the aStartChild/aEndChild range, calls StyleNewChildren
+ * on their flattened tree parents. This is used when content is inserted
+ * into the document and we decide that we cannot do lazy frame construction.
+ * It handles children being rebound to different insertion points by calling
+ * StyleNewChildren on each child's flattened tree parent. Only used when we
+ * are styled by Servo.
+ */
+ void StyleNewChildRange(nsIContent* aStartChild, nsIContent* aEndChild);
+
+ public:
+ /**
+ * Lazy frame construction is controlled by the InsertionKind parameter of
+ * nsCSSFrameConstructor::ContentAppended/Inserted. It is true for all
+ * inserts/appends as passed from the presshell, except for the insert of the
+ * root element, which is always non-lazy.
+ *
+ * If we construct lazily, then we add NODE_NEEDS_FRAME bits to the newly
+ * inserted/appended nodes and adds NODE_DESCENDANTS_NEED_FRAMES bits to the
+ * container and up along the parent chain until it hits the root or another
+ * node with that bit set. Then it posts a restyle event to ensure that a
+ * flush happens to construct those frames.
+ *
+ * When the flush happens the RestyleManager walks the dirty nodes during
+ * ProcessPostTraversal, and ends up calling Content{Appended,Inserted} with
+ * InsertionKind::Sync in ProcessRestyledFrames.
+ *
+ * If a node is removed from the document then we don't bother unsetting any
+ * of the lazy bits that might be set on it, its descendants, or any of its
+ * ancestor nodes because that is a slow operation, the work might be wasted
+ * if another node gets inserted in its place, and we can clear the bits
+ * quicker by processing the content tree from top down the next time we
+ * reconstruct frames. (We do clear the bits when BindToTree is called on any
+ * nsIContent; so any nodes added to the document will not have any lazy bits
+ * set.)
+ */
+
+ // If the insertion kind is Async then frame construction of the new children
+ // can be done lazily.
+ void ContentAppended(nsIContent* aFirstNewContent, InsertionKind);
+
+ // If the insertion kind is Async then frame construction of the new child
+ // can be done lazily.
+ void ContentInserted(nsIContent* aChild, InsertionKind);
+
+ // Like ContentInserted but handles inserting the children in the range
+ // [aStartChild, aEndChild). aStartChild must be non-null. aEndChild may be
+ // null to indicate the range includes all kids after aStartChild.
+ //
+ // If aInsertionKind is Async then frame construction of the new children can
+ // be done lazily. It is only allowed to be Async when inserting a single
+ // node.
+ void ContentRangeInserted(nsIContent* aStartChild, nsIContent* aEndChild,
+ InsertionKind aInsertionKind);
+
+ enum RemoveFlags {
+ REMOVE_CONTENT,
+ REMOVE_FOR_RECONSTRUCTION,
+ };
+
+ /**
+ * Recreate or destroy frames for aChild.
+ *
+ * aFlags == REMOVE_CONTENT means aChild has been removed from the document.
+ * aFlags == REMOVE_FOR_RECONSTRUCTION means the caller will reconstruct the
+ * frames later.
+ *
+ * In both the above cases, this method will in some cases try to reconstruct
+ * frames on some ancestor of aChild. This can happen regardless of the value
+ * of aFlags.
+ *
+ * The return value indicates whether this "reconstruct an ancestor" action
+ * took place. If true is returned, that means that the frame subtree rooted
+ * at some ancestor of aChild's frame was destroyed and will be reconstructed
+ * async.
+ */
+ bool ContentRemoved(nsIContent* aChild, nsIContent* aOldNextSibling,
+ RemoveFlags aFlags);
+
+ void CharacterDataChanged(nsIContent* aContent,
+ const CharacterDataChangeInfo& aInfo);
+
+ // If aContent is a text node that has been optimized away due to being
+ // whitespace next to a block boundary (or for some other reason), ensure that
+ // a frame for it is created the next time frames are flushed, if it can
+ // possibly have a frame at all.
+ //
+ // Returns whether there are chances for the frame to be unsuppressed.
+ bool EnsureFrameForTextNodeIsCreatedAfterFlush(
+ mozilla::dom::CharacterData* aContent);
+
+ // Should be called when a frame is going to be destroyed and
+ // WillDestroyFrameTree hasn't been called yet.
+ void NotifyDestroyingFrame(nsIFrame* aFrame);
+
+ void RecalcQuotesAndCounters();
+
+ // Called when any counter style is changed.
+ void NotifyCounterStylesAreDirty();
+
+ // Gets called when the presshell is destroying itself and also
+ // when we tear down our frame tree to reconstruct it
+ void WillDestroyFrameTree();
+
+ /**
+ * Destroy the frames for aContent. Note that this may destroy frames
+ * for an ancestor instead.
+ *
+ * Returns whether a reconstruct was posted for any ancestor.
+ */
+ bool DestroyFramesFor(nsIContent* aContent);
+
+ // Request to create a continuing frame. This method never returns null.
+ nsIFrame* CreateContinuingFrame(nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ bool aIsFluid = true);
+
+ void SetNextPageContentFramePageName(const nsAtom* aAtom) {
+ MOZ_ASSERT(!mNextPageContentFramePageName,
+ "PageContentFrame page name was already set");
+ mNextPageContentFramePageName = aAtom;
+ }
+
+ // Copy over fixed frames from aParentFrame's prev-in-flow
+ nsresult ReplicateFixedFrames(nsPageContentFrame* aParentFrame);
+
+ /**
+ * Get the insertion point for aChild.
+ */
+ InsertionPoint GetInsertionPoint(nsIContent* aChild);
+
+ /**
+ * Return the insertion frame of the primary frame of aContent, or its nearest
+ * ancestor that isn't display:contents.
+ */
+ nsContainerFrame* GetContentInsertionFrameFor(nsIContent* aContent);
+
+ // GetInitialContainingBlock() is deprecated in favor of
+ // GetRootElementFrame(); nsIFrame* GetInitialContainingBlock() { return
+ // mRootElementFrame; } This returns the outermost frame for the root element
+ nsContainerFrame* GetRootElementFrame() { return mRootElementFrame; }
+ // This returns the frame for the root element that does not
+ // have a psuedo-element style
+ nsIFrame* GetRootElementStyleFrame() { return mRootElementStyleFrame; }
+ nsPageSequenceFrame* GetPageSequenceFrame() { return mPageSequenceFrame; }
+ // Returns the outermost canvas frame. There's usually one per document, but
+ // if but if we're in printing / paginated mode we might have multiple: one
+ // per page plus the background one.
+ nsCanvasFrame* GetCanvasFrame() { return mCanvasFrame; }
+ // Get the frame that is the parent of the root element's frame.
+ nsCanvasFrame* GetDocElementContainingBlock() {
+ return mDocElementContainingBlock;
+ }
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
+
+#if defined(ACCESSIBILITY) || defined(MOZ_LAYOUT_DEBUGGER)
+ // Exposed only for nsLayoutUtils::GetMarkerSpokenText and
+ // nsLayoutDebuggingTools to use.
+ mozilla::ContainStyleScopeManager& GetContainStyleScopeManager() {
+ return mContainStyleScopeManager;
+ }
+#endif
+
+ private:
+ struct FrameConstructionItem;
+ class FrameConstructionItemList;
+
+ mozilla::PrintedSheetFrame* ConstructPrintedSheetFrame(
+ PresShell* aPresShell, nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevSheetFrame);
+
+ nsContainerFrame* ConstructPageFrame(PresShell* aPresShell,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevPageFrame,
+ nsCanvasFrame*& aCanvasFrame);
+
+ void InitAndRestoreFrame(const nsFrameConstructorState& aState,
+ nsIContent* aContent, nsContainerFrame* aParentFrame,
+ nsIFrame* aNewFrame, bool aAllowCounters = true);
+
+ already_AddRefed<ComputedStyle> ResolveComputedStyle(nsIContent* aContent);
+
+ enum class ItemFlag : uint8_t {
+ // Allow page-break before and after items to be created if the
+ // style asks for them.
+ AllowPageBreak,
+ IsGeneratedContent,
+ IsWithinSVGText,
+ // The item allows items to be created for SVG <textPath> children.
+ AllowTextPathChild,
+ // The item is content created by an nsIAnonymousContentCreator frame.
+ IsAnonymousContentCreatorContent,
+ // The item will be the rendered legend of a <fieldset>.
+ IsForRenderedLegend,
+ // This will be an outside ::marker.
+ IsForOutsideMarker,
+ };
+
+ using ItemFlags = mozilla::EnumSet<ItemFlag>;
+
+ // Add the frame construction items for the given aContent and aParentFrame
+ // to the list. This might add more than one item in some rare cases.
+ // If aSuppressWhiteSpaceOptimizations is true, optimizations that
+ // may suppress the construction of white-space-only text frames
+ // must be skipped for these items and items around them.
+ void AddFrameConstructionItems(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ bool aSuppressWhiteSpaceOptimizations,
+ const ComputedStyle& aParentStyle,
+ const InsertionPoint& aInsertion,
+ FrameConstructionItemList& aItems,
+ ItemFlags = {});
+
+ // Helper method for AddFrameConstructionItems etc.
+ // Unsets the need-frame/restyle bits on aContent.
+ // return true iff we should attempt to create frames for aContent.
+ bool ShouldCreateItemsForChild(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame);
+
+ // Construct the frames for the document element. This can return null if the
+ // document element is display:none, or if it's an SVG element that's not
+ // <svg>, etc.
+ nsIFrame* ConstructDocElementFrame(Element* aDocElement);
+
+ // Set up our mDocElementContainingBlock correctly for the given root
+ // content.
+ void SetUpDocElementContainingBlock(nsIContent* aDocElement);
+
+ /**
+ * CreateAttributeContent creates a single content/frame combination for an
+ * |attr(foo)| generated content.
+ *
+ * @param aParentContent the parent content for the generated content (that
+ * is, the originating element).
+ * @param aParentFrame the parent frame for the generated frame
+ * @param aAttrNamespace the namespace of the attribute in question
+ * @param aAttrName the localname of the attribute
+ * @param aComputedStyle the style to use
+ * @param aGeneratedContent the array of generated content to append the
+ * created content to.
+ * @param [out] aNewContent the content node we create
+ * @param [out] aNewFrame the new frame we create
+ */
+ void CreateAttributeContent(const Element& aParentContent,
+ nsIFrame* aParentFrame, int32_t aAttrNamespace,
+ nsAtom* aAttrName, ComputedStyle* aComputedStyle,
+ nsCOMArray<nsIContent>& aGeneratedContent,
+ nsIContent** aNewContent, nsIFrame** aNewFrame);
+
+ /**
+ * Create a text node containing the given string. If aText is non-null
+ * then we also set aText to the returned node.
+ */
+ already_AddRefed<nsIContent> CreateGenConTextNode(
+ nsFrameConstructorState& aState, const nsAString& aString,
+ mozilla::UniquePtr<nsGenConInitializer> aInitializer);
+
+ /**
+ * Create a content node for the given generated content style.
+ * The caller takes care of making it SetIsNativeAnonymousRoot, binding it
+ * to the document, and creating frames for it.
+ * @param aOriginatingElement is the node that has the before/after style.
+ * @param aComputedStyle is the 'before' or 'after' pseudo-element style.
+ * @param aContentIndex is the index of the content item to create.
+ * @param aAddChild callback to be called for each generated content child.
+ */
+ void CreateGeneratedContent(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ ComputedStyle& aPseudoStyle, uint32_t aContentIndex,
+ const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
+
+ /**
+ * Create child content nodes for a ::marker from its 'list-style-*' values.
+ */
+ void CreateGeneratedContentFromListStyle(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ const ComputedStyle& aPseudoStyle,
+ const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
+ /**
+ * Create child content nodes for a ::marker from its 'list-style-type'.
+ */
+ void CreateGeneratedContentFromListStyleType(
+ nsFrameConstructorState& aState, Element& aOriginatingElement,
+ const ComputedStyle& aPseudoStyle,
+ const mozilla::FunctionRef<void(nsIContent*)> aAddChild);
+
+ // aParentFrame may be null; this method doesn't use it directly in any case.
+ void CreateGeneratedContentItem(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ Element& aOriginatingElement, ComputedStyle&,
+ PseudoStyleType aPseudoElement,
+ FrameConstructionItemList& aItems,
+ ItemFlags aExtraFlags = {});
+
+ // This method is called by ContentAppended() and ContentRangeInserted() when
+ // appending flowed frames to a parent's principal child list. It handles the
+ // case where the parent is the trailing inline of an ib-split or is the last
+ // continuation of a ::-moz-column-content in an nsColumnSetFrame.
+ //
+ // This method can change aFrameList: it can chop off the beginning and put it
+ // in aParentFrame while either putting the remainder into an ib-split sibling
+ // of aParentFrame or creating aParentFrame's column-span siblings for the
+ // remainder.
+ //
+ // aPrevSibling must be the frame after which aFrameList is to be placed on
+ // aParentFrame's principal child list. It may be null if aFrameList is being
+ // added at the beginning of the child list.
+ void AppendFramesToParent(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList, nsIFrame* aPrevSibling,
+ bool aIsRecursiveCall = false);
+
+ // BEGIN TABLE SECTION
+ /**
+ * Construct a table wrapper frame. This is the FrameConstructionData
+ * callback used for the job.
+ */
+ nsIFrame* ConstructTable(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * FrameConstructionData callback for constructing table rows and row groups.
+ */
+ nsIFrame* ConstructTableRowOrRowGroup(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * FrameConstructionData callback used for constructing table columns.
+ */
+ nsIFrame* ConstructTableCol(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * FrameConstructionData callback used for constructing table cells.
+ */
+ nsIFrame* ConstructTableCell(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ private:
+ /* An enum of possible parent types for anonymous table or ruby object
+ construction */
+ enum ParentType {
+ eTypeBlock = 0, /* This includes all non-table-related frames */
+ eTypeRow,
+ eTypeRowGroup,
+ eTypeColGroup,
+ eTypeTable,
+ eTypeRuby,
+ eTypeRubyBase,
+ eTypeRubyBaseContainer,
+ eTypeRubyText,
+ eTypeRubyTextContainer,
+ eParentTypeCount
+ };
+
+ /* 4 bits is enough to handle our ParentType values */
+#define FCDATA_PARENT_TYPE_OFFSET 28
+ /* Macro to get the desired parent type out of an mBits member of
+ FrameConstructionData */
+#define FCDATA_DESIRED_PARENT_TYPE(_bits) \
+ ParentType((_bits) >> FCDATA_PARENT_TYPE_OFFSET)
+ /* Macro to create FrameConstructionData bits out of a desired parent type */
+#define FCDATA_DESIRED_PARENT_TYPE_TO_BITS(_type) \
+ (((uint32_t)(_type)) << FCDATA_PARENT_TYPE_OFFSET)
+
+ /* Get the parent type that aParentFrame has. */
+ static ParentType GetParentType(nsIFrame* aParentFrame) {
+ return GetParentType(aParentFrame->Type());
+ }
+
+ /* Get the parent type for the given LayoutFrameType */
+ static ParentType GetParentType(mozilla::LayoutFrameType aFrameType);
+
+ static bool IsRubyParentType(ParentType aParentType) {
+ return (aParentType == eTypeRuby || aParentType == eTypeRubyBase ||
+ aParentType == eTypeRubyBaseContainer ||
+ aParentType == eTypeRubyText ||
+ aParentType == eTypeRubyTextContainer);
+ }
+
+ static bool IsTableParentType(ParentType aParentType) {
+ return (aParentType == eTypeTable || aParentType == eTypeRow ||
+ aParentType == eTypeRowGroup || aParentType == eTypeColGroup);
+ }
+
+ /* A constructor function that just creates an nsIFrame object. The caller
+ is responsible for initializing the object, adding it to frame lists,
+ constructing frames for the children, etc.
+
+ @param PresShell the presshell whose arena should be used to allocate
+ the frame.
+ @param ComputedStyle the style to use for the frame. */
+ using FrameCreationFunc = nsIFrame* (*)(PresShell*, ComputedStyle*);
+ using ContainerFrameCreationFunc = nsContainerFrame* (*)(PresShell*,
+ ComputedStyle*);
+ using BlockFrameCreationFunc = nsBlockFrame* (*)(PresShell*, ComputedStyle*);
+
+ /* A function that can be used to get a FrameConstructionData. Such
+ a function is allowed to return null.
+
+ @param nsIContent the node for which the frame is being constructed.
+ @param ComputedStyle the style to be used for the frame.
+ */
+ struct FrameConstructionData;
+ using FrameConstructionDataGetter =
+ const FrameConstructionData* (*)(const Element&, ComputedStyle&);
+
+ /* A constructor function that's used for complicated construction tasks.
+ This is expected to create the new frame, initialize it, add whatever
+ needs to be added to aFrameList (XXXbz is that really necessary? Could
+ caller add? Might there be cases when the returned frame or its
+ placeholder is not the thing that ends up in aFrameList? If not, would
+ it be safe to do the add into the frame construction state after
+ processing kids? Look into this as a followup!), process children as
+ needed, etc. It is NOT expected to deal with setting the frame on the
+ content.
+
+ @param aState the frame construction state to use.
+ @param aItem the frame construction item to use
+ @param aParentFrame the frame to set as the parent of the
+ newly-constructed frame.
+ @param aStyleDisplay the display struct from aItem's mComputedStyle
+ @param aFrameList the frame list to add the new frame (or its
+ placeholder) to.
+ @return the frame that was constructed. This frame is what the caller
+ will set as the frame on the content. Guaranteed non-null.
+ */
+ using FrameFullConstructor =
+ nsIFrame* (nsCSSFrameConstructor::*)(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ /* Bits that modify the way a FrameConstructionData is handled */
+
+ /* If the FCDATA_SKIP_FRAMESET bit is set, then the frame created should not
+ be set as the primary frame on the content node. This should only be used
+ in very rare cases when we create more than one frame for a given content
+ node. */
+#define FCDATA_SKIP_FRAMESET 0x1
+ /* If the FCDATA_FUNC_IS_DATA_GETTER bit is set, then the mFunc of the
+ FrameConstructionData is a getter function that can be used to get the
+ actual FrameConstructionData to use. */
+#define FCDATA_FUNC_IS_DATA_GETTER 0x2
+ /* If the FCDATA_FUNC_IS_FULL_CTOR bit is set, then the FrameConstructionData
+ has an mFullConstructor. In this case, there is no relevant mData or
+ mFunc */
+#define FCDATA_FUNC_IS_FULL_CTOR 0x4
+ /* If FCDATA_DISALLOW_OUT_OF_FLOW is set, do not allow the frame to
+ float or be absolutely positioned. This can also be used with
+ FCDATA_FUNC_IS_FULL_CTOR to indicate what the full-constructor
+ function will do. */
+#define FCDATA_DISALLOW_OUT_OF_FLOW 0x8
+ /* If FCDATA_FORCE_NULL_ABSPOS_CONTAINER is set, make sure to push a
+ null absolute containing block before processing children for this
+ frame. If this is not set, the frame will be pushed as the
+ absolute containing block as needed, based on its style */
+#define FCDATA_FORCE_NULL_ABSPOS_CONTAINER 0x10
+ /* If FCDATA_WRAP_KIDS_IN_BLOCKS is set, the inline kids of the frame
+ will be wrapped in blocks. This is only usable for MathML at the
+ moment. */
+#define FCDATA_WRAP_KIDS_IN_BLOCKS 0x20
+ /* If FCDATA_SUPPRESS_FRAME is set, no frame should be created for the
+ content. If this bit is set, nothing else in the struct needs to be
+ set. */
+#define FCDATA_SUPPRESS_FRAME 0x40
+ /* If FCDATA_MAY_NEED_SCROLLFRAME is set, the new frame should be wrapped in
+ a scrollframe if its overflow type so requires. */
+#define FCDATA_MAY_NEED_SCROLLFRAME 0x80
+ /* If FCDATA_IS_POPUP is set, the new frame is a XUL popup frame. These need
+ some really weird special handling. */
+#define FCDATA_IS_POPUP 0x100
+ /* If FCDATA_SKIP_ABSPOS_PUSH is set, don't push this frame as an
+ absolute containing block, no matter what its style says. */
+#define FCDATA_SKIP_ABSPOS_PUSH 0x200
+ /* If FCDATA_DISALLOW_GENERATED_CONTENT is set, then don't allow generated
+ content when processing kids of this frame. This should not be used with
+ FCDATA_FUNC_IS_FULL_CTOR */
+#define FCDATA_DISALLOW_GENERATED_CONTENT 0x400
+ /* If FCDATA_IS_TABLE_PART is set, then the frame is some sort of
+ table-related thing and we should not attempt to fetch a table-cell parent
+ for it if it's inside another table-related frame. */
+#define FCDATA_IS_TABLE_PART 0x800
+ /* If FCDATA_IS_INLINE is set, then the frame is a non-replaced CSS
+ inline box. */
+#define FCDATA_IS_INLINE 0x1000
+ /* If FCDATA_IS_LINE_PARTICIPANT is set, the frame is something that will
+ return true for IsLineParticipant() */
+#define FCDATA_IS_LINE_PARTICIPANT 0x2000
+ /* If FCDATA_IS_LINE_BREAK is set, the frame is something that will
+ induce a line break boundary before and after itself. */
+#define FCDATA_IS_LINE_BREAK 0x4000
+ /* If FCDATA_ALLOW_BLOCK_STYLES is set, allow block styles when processing
+ children of a block (i.e. allow ::first-letter/line).
+ This should not be used with FCDATA_FUNC_IS_FULL_CTOR. */
+#define FCDATA_ALLOW_BLOCK_STYLES 0x8000
+ /* If FCDATA_USE_CHILD_ITEMS is set, then use the mChildItems in the relevant
+ FrameConstructionItem instead of trying to process the content's children.
+ This can be used with or without FCDATA_FUNC_IS_FULL_CTOR.
+ The child items might still need table pseudo processing. */
+#define FCDATA_USE_CHILD_ITEMS 0x10000
+ /* If FCDATA_FORCED_NON_SCROLLABLE_BLOCK is set, then this block
+ would have been scrollable but has been forced to be
+ non-scrollable due to being in a paginated context. */
+#define FCDATA_FORCED_NON_SCROLLABLE_BLOCK 0x20000
+ /* If FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, then create a
+ block formatting context wrapper around the kids of this frame
+ using the FrameConstructionData's mPseudoAtom for its anonymous
+ box type. */
+#define FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS 0x40000
+ /* If FCDATA_IS_SVG_TEXT is set, then this text frame is a descendant of
+ an SVG text frame. */
+#define FCDATA_IS_SVG_TEXT 0x80000
+ /**
+ * If FCDATA_ALLOW_GRID_FLEX_COLUMN is set, then we should create a
+ * grid/flex/column container instead of a block wrapper when the styles says
+ * so. This bit is meaningful only if FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS
+ * is also set.
+ */
+#define FCDATA_ALLOW_GRID_FLEX_COLUMN 0x200000
+ /**
+ * Whether the kids of this FrameConstructionData should be flagged as having
+ * a wrapper anon box parent. This should only be set if
+ * FCDATA_USE_CHILD_ITEMS is set.
+ */
+#define FCDATA_IS_WRAPPER_ANON_BOX 0x400000
+
+ /* Structure representing information about how a frame should be
+ constructed. */
+ struct FrameConstructionData {
+ // We have exactly one of three types of functions, so use a union for
+ // better cache locality.
+ union Func {
+ FrameCreationFunc mCreationFunc;
+ FrameConstructionDataGetter mDataGetter;
+ FrameFullConstructor mFullConstructor;
+
+ explicit constexpr Func(FrameCreationFunc aFunc) : mCreationFunc(aFunc) {}
+ explicit constexpr Func(FrameConstructionDataGetter aDataGetter)
+ : mDataGetter(aDataGetter) {}
+ explicit constexpr Func(FrameFullConstructor aCtor)
+ : mFullConstructor(aCtor) {}
+ } mFunc;
+ // Flag bits that can modify the way the construction happens
+ const uint32_t mBits = 0;
+ // For cases when FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS is set, the
+ // anonymous box type to use for that wrapper.
+ PseudoStyleType const mAnonBoxPseudo = PseudoStyleType::NotPseudo;
+
+ constexpr FrameConstructionData() : FrameConstructionData(nullptr) {}
+
+ MOZ_IMPLICIT constexpr FrameConstructionData(std::nullptr_t,
+ uint32_t aBits = 0)
+ : mFunc(static_cast<FrameCreationFunc>(nullptr)), mBits(aBits) {}
+
+ MOZ_IMPLICIT constexpr FrameConstructionData(
+ FrameCreationFunc aCreationFunc, uint32_t aBits = 0)
+ : mFunc(aCreationFunc), mBits(aBits) {}
+ constexpr FrameConstructionData(FrameCreationFunc aCreationFunc,
+ uint32_t aBits,
+ PseudoStyleType aAnonBoxPseudo)
+ : mFunc(aCreationFunc),
+ mBits(aBits | FCDATA_CREATE_BLOCK_WRAPPER_FOR_ALL_KIDS),
+ mAnonBoxPseudo(aAnonBoxPseudo) {}
+ MOZ_IMPLICIT constexpr FrameConstructionData(
+ FrameConstructionDataGetter aDataGetter, uint32_t aBits = 0)
+ : mFunc(aDataGetter),
+ mBits(aBits | FCDATA_FUNC_IS_DATA_GETTER),
+ mAnonBoxPseudo(PseudoStyleType::NotPseudo) {}
+ MOZ_IMPLICIT constexpr FrameConstructionData(FrameFullConstructor aCtor,
+ uint32_t aBits = 0)
+ : mFunc(aCtor),
+ mBits(aBits | FCDATA_FUNC_IS_FULL_CTOR),
+ mAnonBoxPseudo(PseudoStyleType::NotPseudo) {}
+ };
+
+ /* Structure representing a mapping of an atom to a FrameConstructionData.
+ This can be used with non-static atoms, assuming that the nsAtom* is
+ stored somewhere that this struct can point to (that is, a static
+ nsAtom*) and that it's allocated before the struct is ever used. */
+ struct FrameConstructionDataByTag {
+ const nsStaticAtom* const mTag;
+ const FrameConstructionData mData;
+ };
+
+ /* Structure representing a mapping of an integer to a
+ FrameConstructionData. There are no magic integer values here. */
+ struct FrameConstructionDataByInt {
+ /* Could be used for display or whatever else */
+ const int32_t mInt;
+ const FrameConstructionData mData;
+ };
+
+ struct FrameConstructionDataByDisplay {
+#ifdef DEBUG
+ const mozilla::StyleDisplay mDisplay;
+#endif
+ const FrameConstructionData mData;
+ };
+
+ /* Structure that has a FrameConstructionData and style pseudo-type
+ for a table pseudo-frame */
+ struct PseudoParentData {
+ const FrameConstructionData mFCData;
+ mozilla::PseudoStyleType const mPseudoType;
+ };
+ /* Array of such structures that we use to properly construct table
+ pseudo-frames as needed */
+ static const PseudoParentData sPseudoParentData[eParentTypeCount];
+
+ const FrameConstructionData* FindDataForContent(nsIContent&, ComputedStyle&,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags);
+
+ // aParentFrame might be null. If it is, that means it was an inline frame.
+ static const FrameConstructionData* FindTextData(const Text&,
+ nsIFrame* aParentFrame);
+ const FrameConstructionData* FindElementData(const Element&, ComputedStyle&,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags);
+ const FrameConstructionData* FindElementTagData(const Element&,
+ ComputedStyle&,
+ nsIFrame* aParentFrame,
+ ItemFlags aFlags);
+
+ /* A function that takes an integer, content, style, and array of
+ FrameConstructionDataByInts and finds the appropriate frame construction
+ data to use and returns it. This can return null if none of the integers
+ match or if the matching integer has a FrameConstructionDataGetter that
+ returns null. */
+ static const FrameConstructionData* FindDataByInt(
+ int32_t aInt, const Element&, ComputedStyle&,
+ const FrameConstructionDataByInt* aDataPtr, uint32_t aDataLength);
+
+ /**
+ * A function that takes a tag, content, style, and array of
+ * FrameConstructionDataByTags and finds the appropriate frame construction
+ * data to use and returns it.
+ *
+ * This can return null if none of the tags match or if the matching tag has a
+ * FrameConstructionDataGetter that returns null. In the case that the tags
+ * actually match, aTagFound will be true, even if the return value is null.
+ */
+ static const FrameConstructionData* FindDataByTag(
+ const Element& aElement, ComputedStyle& aComputedStyle,
+ const FrameConstructionDataByTag* aDataPtr, uint32_t aDataLength);
+
+ /* A class representing a list of FrameConstructionItems. Instances of this
+ class are only created as AutoFrameConstructionItemList, or as a member
+ of FrameConstructionItem. */
+ class FrameConstructionItemList {
+ public:
+ void Reset(nsCSSFrameConstructor* aFCtor) {
+ Destroy(aFCtor);
+ this->~FrameConstructionItemList();
+ new (this) FrameConstructionItemList();
+ }
+
+ void SetLineBoundaryAtStart(bool aBoundary) {
+ mLineBoundaryAtStart = aBoundary;
+ }
+ void SetLineBoundaryAtEnd(bool aBoundary) {
+ mLineBoundaryAtEnd = aBoundary;
+ }
+ void SetParentHasNoShadowDOM(bool aValue) {
+ mParentHasNoShadowDOM = aValue;
+ }
+ bool HasLineBoundaryAtStart() { return mLineBoundaryAtStart; }
+ bool HasLineBoundaryAtEnd() { return mLineBoundaryAtEnd; }
+ bool ParentHasNoShadowDOM() { return mParentHasNoShadowDOM; }
+ bool IsEmpty() const { return mItems.isEmpty(); }
+ bool AreAllItemsInline() const { return mInlineCount == mItemCount; }
+ bool AreAllItemsBlock() const { return mBlockCount == mItemCount; }
+ bool AllWantParentType(ParentType aDesiredParentType) const {
+ return mDesiredParentCounts[aDesiredParentType] == mItemCount;
+ }
+
+ // aSuppressWhiteSpaceOptimizations is true if optimizations that
+ // skip constructing whitespace frames for this item or items
+ // around it cannot be performed.
+ // Also, the return value is always non-null, thanks to infallible 'new'.
+ FrameConstructionItem* AppendItem(
+ nsCSSFrameConstructor* aFCtor, const FrameConstructionData* aFCData,
+ nsIContent* aContent, already_AddRefed<ComputedStyle>&& aComputedStyle,
+ bool aSuppressWhiteSpaceOptimizations) {
+ FrameConstructionItem* item = new (aFCtor)
+ FrameConstructionItem(aFCData, aContent, std::move(aComputedStyle),
+ aSuppressWhiteSpaceOptimizations);
+ mItems.insertBack(item);
+ ++mItemCount;
+ ++mDesiredParentCounts[item->DesiredParentType()];
+ return item;
+ }
+
+ // Arguments are the same as AppendItem().
+ FrameConstructionItem* PrependItem(
+ nsCSSFrameConstructor* aFCtor, const FrameConstructionData* aFCData,
+ nsIContent* aContent, already_AddRefed<ComputedStyle>&& aComputedStyle,
+ bool aSuppressWhiteSpaceOptimizations) {
+ FrameConstructionItem* item = new (aFCtor)
+ FrameConstructionItem(aFCData, aContent, std::move(aComputedStyle),
+ aSuppressWhiteSpaceOptimizations);
+ mItems.insertFront(item);
+ ++mItemCount;
+ ++mDesiredParentCounts[item->DesiredParentType()];
+ return item;
+ }
+
+ void InlineItemAdded() { ++mInlineCount; }
+ void BlockItemAdded() { ++mBlockCount; }
+
+ class Iterator {
+ public:
+ explicit Iterator(FrameConstructionItemList& aList)
+ : mCurrent(aList.mItems.getFirst()), mList(aList) {}
+ Iterator(const Iterator& aOther) = default;
+
+ bool operator==(const Iterator& aOther) const {
+ MOZ_ASSERT(&mList == &aOther.mList, "Iterators for different lists?");
+ return mCurrent == aOther.mCurrent;
+ }
+ bool operator!=(const Iterator& aOther) const {
+ return !(*this == aOther);
+ }
+ Iterator& operator=(const Iterator& aOther) {
+ MOZ_ASSERT(&mList == &aOther.mList, "Iterators for different lists?");
+ mCurrent = aOther.mCurrent;
+ return *this;
+ }
+
+ FrameConstructionItemList* List() { return &mList; }
+
+ FrameConstructionItem& item() {
+ MOZ_ASSERT(!IsDone(), "Should have checked IsDone()!");
+ return *mCurrent;
+ }
+
+ const FrameConstructionItem& item() const {
+ MOZ_ASSERT(!IsDone(), "Should have checked IsDone()!");
+ return *mCurrent;
+ }
+
+ bool IsDone() const { return mCurrent == nullptr; }
+ bool AtStart() const { return mCurrent == mList.mItems.getFirst(); }
+ void Next() {
+ NS_ASSERTION(!IsDone(), "Should have checked IsDone()!");
+ mCurrent = mCurrent->getNext();
+ }
+ void Prev() {
+ NS_ASSERTION(!AtStart(), "Should have checked AtStart()!");
+ mCurrent = mCurrent ? mCurrent->getPrevious() : mList.mItems.getLast();
+ }
+ void SetToEnd() { mCurrent = nullptr; }
+
+ // Skip over all items that want the given parent type. Return whether
+ // the iterator is done after doing that. The iterator must not be done
+ // when this is called.
+ inline bool SkipItemsWantingParentType(ParentType aParentType);
+
+ // Skip over all items that want a parent type different from the given
+ // one. Return whether the iterator is done after doing that. The
+ // iterator must not be done when this is called.
+ inline bool SkipItemsNotWantingParentType(ParentType aParentType);
+
+ // Skip over non-replaced inline frames and positioned frames.
+ // Return whether the iterator is done after doing that.
+ // The iterator must not be done when this is called.
+ inline bool SkipItemsThatNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState, bool aIsWebkitBox);
+
+ // Skip to the first frame that is a non-replaced inline or is
+ // positioned. Return whether the iterator is done after doing that.
+ // The iterator must not be done when this is called.
+ inline bool SkipItemsThatDontNeedAnonFlexOrGridItem(
+ const nsFrameConstructorState& aState, bool aIsWebkitBox);
+
+ // Skip over all items that do not want a ruby parent. Return whether
+ // the iterator is done after doing that. The iterator must not be done
+ // when this is called.
+ inline bool SkipItemsNotWantingRubyParent();
+
+ // Skip over whitespace. Return whether the iterator is done after doing
+ // that. The iterator must not be done, and must be pointing to a
+ // whitespace item when this is called.
+ inline bool SkipWhitespace(nsFrameConstructorState& aState);
+
+ // Remove the item pointed to by this iterator from its current list and
+ // Append it to aTargetList. This iterator is advanced to point to the
+ // next item in its list. aIter must not be done. aTargetList must not
+ // be the list this iterator is iterating over..
+ void AppendItemToList(FrameConstructionItemList& aTargetList);
+
+ // As above, but moves all items starting with this iterator until we
+ // get to aEnd; the item pointed to by aEnd is not stolen. This method
+ // might have optimizations over just looping and doing StealItem for
+ // some special cases. After this method returns, this iterator will
+ // point to the item aEnd points to now; aEnd is not modified.
+ // aTargetList must not be the list this iterator is iterating over.
+ void AppendItemsToList(nsCSSFrameConstructor* aFCtor,
+ const Iterator& aEnd,
+ FrameConstructionItemList& aTargetList);
+
+ // Insert aItem in this iterator's list right before the item pointed to
+ // by this iterator. After the insertion, this iterator will continue to
+ // point to the item it now points to (the one just after the
+ // newly-inserted item). This iterator is allowed to be done; in that
+ // case this call just appends the given item to the list.
+ void InsertItem(FrameConstructionItem* aItem);
+
+ // Delete the items between this iterator and aEnd, including the item
+ // this iterator currently points to but not including the item pointed
+ // to by aEnd. When this returns, this iterator will point to the same
+ // item as aEnd. This iterator must not equal aEnd when this method is
+ // called.
+ void DeleteItemsTo(nsCSSFrameConstructor* aFCtor, const Iterator& aEnd);
+
+ private:
+ FrameConstructionItem* mCurrent;
+ FrameConstructionItemList& mList;
+ };
+
+ protected:
+ FrameConstructionItemList()
+ : mInlineCount(0),
+ mBlockCount(0),
+ mItemCount(0),
+ mLineBoundaryAtStart(false),
+ mLineBoundaryAtEnd(false),
+ mParentHasNoShadowDOM(false) {
+ MOZ_COUNT_CTOR(FrameConstructionItemList);
+ memset(mDesiredParentCounts, 0, sizeof(mDesiredParentCounts));
+ }
+
+ void Destroy(nsCSSFrameConstructor* aFCtor) {
+ while (FrameConstructionItem* item = mItems.popFirst()) {
+ item->Delete(aFCtor);
+ }
+ }
+
+ // Prevent stack instances (except as AutoFrameConstructionItemList).
+ friend struct FrameConstructionItem;
+ ~FrameConstructionItemList() {
+ MOZ_COUNT_DTOR(FrameConstructionItemList);
+ MOZ_ASSERT(mItems.isEmpty(), "leaking");
+ }
+
+ private:
+ // Not allocated from the heap!
+ void* operator new(size_t) = delete;
+ void* operator new[](size_t) = delete;
+#ifdef _MSC_VER /* Visual Studio */
+ void operator delete(void*) { MOZ_CRASH("FrameConstructionItemList::del"); }
+#else
+ void operator delete(void*) = delete;
+#endif
+ void operator delete[](void*) = delete;
+ // Placement new is used by Reset().
+ void* operator new(size_t, void* aPtr) { return aPtr; }
+
+ struct UndisplayedItem {
+ UndisplayedItem(nsIContent* aContent, ComputedStyle* aComputedStyle)
+ : mContent(aContent), mComputedStyle(aComputedStyle) {}
+
+ nsIContent* const mContent;
+ RefPtr<ComputedStyle> mComputedStyle;
+ };
+
+ // Adjust our various counts for aItem being added or removed. aDelta
+ // should be either +1 or -1 depending on which is happening.
+ void AdjustCountsForItem(FrameConstructionItem* aItem, int32_t aDelta);
+
+ mozilla::LinkedList<FrameConstructionItem> mItems;
+ uint32_t mInlineCount;
+ uint32_t mBlockCount;
+ uint32_t mItemCount;
+ uint32_t mDesiredParentCounts[eParentTypeCount];
+ // True if there is guaranteed to be a line boundary before the
+ // frames created by these items
+ bool mLineBoundaryAtStart;
+ // True if there is guaranteed to be a line boundary after the
+ // frames created by these items
+ bool mLineBoundaryAtEnd;
+ // True if the parent is guaranteed to have no shadow tree.
+ bool mParentHasNoShadowDOM;
+ };
+
+ /* A struct representing a list of FrameConstructionItems on the stack. */
+ struct MOZ_RAII AutoFrameConstructionItemList final
+ : public FrameConstructionItemList {
+ template <typename... Args>
+ explicit AutoFrameConstructionItemList(nsCSSFrameConstructor* aFCtor,
+ Args&&... args)
+ : FrameConstructionItemList(std::forward<Args>(args)...),
+ mFCtor(aFCtor) {
+ MOZ_ASSERT(mFCtor);
+ }
+ ~AutoFrameConstructionItemList() { Destroy(mFCtor); }
+
+ private:
+ nsCSSFrameConstructor* const mFCtor;
+ };
+
+ typedef FrameConstructionItemList::Iterator FCItemIterator;
+
+ /* A struct representing an item for which frames might need to be
+ * constructed. This contains all the information needed to construct the
+ * frame other than the parent frame and whatever would be stored in the
+ * frame constructor state. You probably want to use
+ * AutoFrameConstructionItem instead of this struct. */
+ struct FrameConstructionItem final
+ : public mozilla::LinkedListElement<FrameConstructionItem> {
+ FrameConstructionItem(const FrameConstructionData* aFCData,
+ nsIContent* aContent,
+ already_AddRefed<ComputedStyle>&& aComputedStyle,
+ bool aSuppressWhiteSpaceOptimizations)
+ : mFCData(aFCData),
+ mContent(aContent),
+ mComputedStyle(std::move(aComputedStyle)),
+ mSuppressWhiteSpaceOptimizations(aSuppressWhiteSpaceOptimizations),
+ mIsText(false),
+ mIsGeneratedContent(false),
+ mIsAllInline(false),
+ mIsBlock(false),
+ mIsPopup(false),
+ mIsLineParticipant(false),
+ mIsRenderedLegend(false) {
+ MOZ_COUNT_CTOR(FrameConstructionItem);
+ }
+
+ void* operator new(size_t, nsCSSFrameConstructor* aFCtor) {
+ return aFCtor->AllocateFCItem();
+ }
+
+ void Delete(nsCSSFrameConstructor* aFCtor) {
+ mChildItems.Destroy(aFCtor);
+ if (mIsGeneratedContent) {
+ mContent->UnbindFromTree();
+ NS_RELEASE(mContent);
+ }
+ this->~FrameConstructionItem();
+ aFCtor->FreeFCItem(this);
+ }
+
+ ParentType DesiredParentType() {
+ return FCDATA_DESIRED_PARENT_TYPE(mFCData->mBits);
+ }
+
+ // Indicates whether (when in a flex or grid container) this item needs
+ // to be wrapped in an anonymous block. (Note that we implement
+ // -webkit-box/-webkit-inline-box using our standard flexbox frame class,
+ // but we use different rules for what gets wrapped. The aIsWebkitBox
+ // parameter here tells us whether to use those different rules.)
+ bool NeedsAnonFlexOrGridItem(const nsFrameConstructorState& aState,
+ bool aIsWebkitBox);
+
+ // Don't call this unless the frametree really depends on the answer!
+ // Especially so for generated content, where we don't want to reframe
+ // things.
+ bool IsWhitespace(nsFrameConstructorState& aState) const;
+
+ bool IsLineBoundary() const {
+ return mIsBlock || (mFCData->mBits & FCDATA_IS_LINE_BREAK);
+ }
+
+ // Child frame construction items.
+ FrameConstructionItemList mChildItems;
+
+ // The FrameConstructionData to use.
+ const FrameConstructionData* mFCData;
+ // The nsIContent node to use when initializing the new frame.
+ nsIContent* mContent;
+ // The style to use for creating the new frame.
+ RefPtr<ComputedStyle> mComputedStyle;
+ // Whether optimizations to skip constructing textframes around
+ // this content need to be suppressed.
+ bool mSuppressWhiteSpaceOptimizations : 1;
+ // Whether this is a text content item.
+ bool mIsText : 1;
+ // Whether this is a generated content container.
+ // If it is, mContent is a strong pointer.
+ bool mIsGeneratedContent : 1;
+ // Whether construction from this item will create only frames that are
+ // IsInlineOutside() in the principal child list. This is not precise, but
+ // conservative: if true the frames will really be inline, whereas if false
+ // they might still all be inline.
+ bool mIsAllInline : 1;
+ // Whether construction from this item will create only frames that are
+ // IsBlockOutside() in the principal child list. This is not precise, but
+ // conservative: if true the frames will really be blocks, whereas if false
+ // they might still be blocks (and in particular, out-of-flows that didn't
+ // find a containing block).
+ bool mIsBlock : 1;
+ // Whether construction from this item will create a popup that needs to
+ // go into the global popup items.
+ bool mIsPopup : 1;
+ // Whether this item should be treated as a line participant
+ bool mIsLineParticipant : 1;
+ // Whether this item is the rendered legend of a <fieldset>
+ bool mIsRenderedLegend : 1;
+
+ private:
+ // Not allocated from the general heap - instead, use the new/Delete APIs
+ // that take a nsCSSFrameConstructor* (which manages our arena allocation).
+ void* operator new(size_t) = delete;
+ void* operator new[](size_t) = delete;
+#ifdef _MSC_VER /* Visual Studio */
+ void operator delete(void*) { MOZ_CRASH("FrameConstructionItem::delete"); }
+#else
+ void operator delete(void*) = delete;
+#endif
+ void operator delete[](void*) = delete;
+ FrameConstructionItem(const FrameConstructionItem& aOther) = delete;
+ // Not allocated from the stack!
+ ~FrameConstructionItem() {
+ MOZ_COUNT_DTOR(FrameConstructionItem);
+ MOZ_ASSERT(mChildItems.IsEmpty(), "leaking");
+ }
+ };
+
+ /**
+ * Convenience struct to assist in managing a temporary FrameConstructionItem
+ * using a local variable. Castable to FrameConstructionItem so that it can
+ * be passed transparently to functions that expect that type.
+ * (This struct exists because FrameConstructionItem is arena-allocated, and
+ * it's nice to abstract away its allocation/deallocation.)
+ */
+ struct MOZ_RAII AutoFrameConstructionItem final {
+ template <typename... Args>
+ explicit AutoFrameConstructionItem(nsCSSFrameConstructor* aFCtor,
+ Args&&... args)
+ : mFCtor(aFCtor),
+ mItem(new(aFCtor)
+ FrameConstructionItem(std::forward<Args>(args)...)) {
+ MOZ_ASSERT(mFCtor);
+ }
+ ~AutoFrameConstructionItem() { mItem->Delete(mFCtor); }
+ operator FrameConstructionItem&() { return *mItem; }
+
+ private:
+ nsCSSFrameConstructor* const mFCtor;
+ FrameConstructionItem* const mItem;
+ };
+
+ /**
+ * Updates the nsFrameConstructorState auto page-name value, and restores the
+ * previous value on destruction.
+ * See https://drafts.csswg.org/css-page-3/#using-named-pages
+ *
+ * To track this, this will automatically add PageValuesProperty to
+ * the frame.
+ *
+ * Note that this does not add PageValuesProperty to the frame when not in a
+ * paginated context.
+ */
+ class MOZ_RAII AutoFrameConstructionPageName final {
+ nsFrameConstructorState& mState;
+ const nsAtom* mNameToRestore;
+
+ public:
+ AutoFrameConstructionPageName(const AutoFrameConstructionPageName&) =
+ delete;
+ AutoFrameConstructionPageName(AutoFrameConstructionPageName&&) = delete;
+ AutoFrameConstructionPageName(nsFrameConstructorState& aState,
+ nsIFrame* const aFrame);
+ ~AutoFrameConstructionPageName();
+ };
+
+ /**
+ * Function to create the anonymous flex or grid items that we need.
+ * If aParentFrame is not a nsFlexContainerFrame or nsGridContainerFrame then
+ * this method is a NOP.
+ * @param aItems the child frame construction items before pseudo creation
+ * @param aParentFrame the parent frame
+ */
+ void CreateNeededAnonFlexOrGridItems(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ enum RubyWhitespaceType {
+ eRubyNotWhitespace,
+ eRubyInterLevelWhitespace,
+ // Includes inter-base and inter-annotation whitespace
+ eRubyInterLeafWhitespace,
+ eRubyInterSegmentWhitespace
+ };
+
+ /**
+ * Function to compute the whitespace type according to the display
+ * values of the previous and the next elements.
+ */
+ static inline RubyWhitespaceType ComputeRubyWhitespaceType(
+ mozilla::StyleDisplay aPrevDisplay, mozilla::StyleDisplay aNextDisplay);
+
+ /**
+ * Function to interpret the type of whitespace between
+ * |aStartIter| and |aEndIter|.
+ */
+ static inline RubyWhitespaceType InterpretRubyWhitespace(
+ nsFrameConstructorState& aState, const FCItemIterator& aStartIter,
+ const FCItemIterator& aEndIter);
+
+ /**
+ * Function to wrap consecutive misparented inline content into
+ * a ruby base box or a ruby text box.
+ */
+ void WrapItemsInPseudoRubyLeafBox(FCItemIterator& aIter,
+ ComputedStyle* aParentStyle,
+ nsIContent* aParentContent);
+
+ /**
+ * Function to wrap consecutive misparented items
+ * into a ruby level container.
+ */
+ inline void WrapItemsInPseudoRubyLevelContainer(
+ nsFrameConstructorState& aState, FCItemIterator& aIter,
+ ComputedStyle* aParentStyle, nsIContent* aParentContent);
+
+ /**
+ * Function to trim leading and trailing whitespaces.
+ */
+ inline void TrimLeadingAndTrailingWhitespaces(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems);
+
+ /**
+ * Function to create internal ruby boxes.
+ */
+ inline void CreateNeededPseudoInternalRubyBoxes(
+ nsFrameConstructorState& aState, FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ /**
+ * Function to create the pseudo intermediate containers we need.
+ * @param aItems the child frame construction items before pseudo creation
+ * @param aParentFrame the parent frame we're creating pseudos for
+ */
+ inline void CreateNeededPseudoContainers(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ /**
+ * Function to wrap consecutive items into a pseudo parent.
+ */
+ inline void WrapItemsInPseudoParent(nsIContent* aParentContent,
+ ComputedStyle* aParentStyle,
+ ParentType aWrapperType,
+ FCItemIterator& aIter,
+ const FCItemIterator& aEndIter);
+
+ /**
+ * Function to create the pseudo siblings we need.
+ */
+ inline void CreateNeededPseudoSiblings(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsIFrame* aParentFrame);
+
+ // END TABLE SECTION
+
+ protected:
+ static nsIFrame* CreatePlaceholderFrameFor(PresShell* aPresShell,
+ nsIContent* aContent,
+ nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIFrame* aPrevInFlow,
+ nsFrameState aTypeBit);
+
+ private:
+ // ConstructSelectFrame puts the new frame in aFrameList and
+ // handles the kids of the select.
+ nsIFrame* ConstructSelectFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ // ConstructFieldSetFrame puts the new frame in aFrameList and
+ // handles the kids of the fieldset
+ nsIFrame* ConstructFieldSetFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ // Creates a block frame wrapping an anonymous ruby frame.
+ nsIFrame* ConstructBlockRubyFrame(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aStyleDisplay,
+ nsFrameList& aFrameList);
+
+ void ConstructTextFrame(const FrameConstructionData* aData,
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ ComputedStyle* aComputedStyle,
+ nsFrameList& aFrameList);
+
+ // If aPossibleTextContent is a text node and doesn't have a frame, append a
+ // frame construction item for it to aItems.
+ void AddTextItemIfNeeded(nsFrameConstructorState& aState,
+ const ComputedStyle& aParentStyle,
+ const InsertionPoint& aInsertion,
+ nsIContent* aPossibleTextContent,
+ FrameConstructionItemList& aItems);
+
+ // If aContent is a text node and doesn't have a frame, try to create a frame
+ // for it.
+ void ReframeTextIfNeeded(nsIContent* aContent);
+
+ enum InsertPageBreakLocation { eBefore, eAfter };
+ inline void AppendPageBreakItem(nsIContent* aContent,
+ FrameConstructionItemList& aItems) {
+ InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eAfter);
+ }
+ inline void PrependPageBreakItem(nsIContent* aContent,
+ FrameConstructionItemList& aItems) {
+ InsertPageBreakItem(aContent, aItems, InsertPageBreakLocation::eBefore);
+ }
+ void InsertPageBreakItem(nsIContent* aContent,
+ FrameConstructionItemList& aItems,
+ InsertPageBreakLocation location);
+
+ // Function to find FrameConstructionData for aElement. Will return
+ // null if aElement is not HTML.
+ // aParentFrame might be null. If it is, that means it was an
+ // inline frame.
+ static const FrameConstructionData* FindHTMLData(const Element&,
+ nsIFrame* aParentFrame,
+ ComputedStyle&);
+ // HTML data-finding helper functions
+ static const FrameConstructionData* FindImgData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindGeneratedImageData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindImgControlData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindSearchControlData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindInputData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindObjectData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindCanvasData(const Element&,
+ ComputedStyle&);
+ // <details> always creates a block per spec.
+ static const FrameConstructionData* FindDetailsData(const Element&,
+ ComputedStyle&);
+
+ /* Construct a frame from the given FrameConstructionItem. This function
+ will handle adding the frame to frame lists, processing children, setting
+ the frame as the primary frame for the item's content, and so forth.
+
+ @param aItem the FrameConstructionItem to use.
+ @param aState the frame construction state to use.
+ @param aParentFrame the frame to set as the parent of the
+ newly-constructed frame.
+ @param aFrameList the frame list to add the new frame (or its
+ placeholder) to.
+ */
+ void ConstructFrameFromItemInternal(FrameConstructionItem& aItem,
+ nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList);
+
+ // The guts of AddFrameConstructionItems
+ // aParentFrame might be null. If it is, that means it was an
+ // inline frame.
+ void AddFrameConstructionItemsInternal(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ bool aSuppressWhiteSpaceOptimizations,
+ ComputedStyle*, ItemFlags,
+ FrameConstructionItemList& aItems);
+
+ /**
+ * Construct frames for the given item list and parent frame, and put the
+ * resulting frames in aFrameList.
+ */
+ void ConstructFramesFromItemList(nsFrameConstructorState& aState,
+ FrameConstructionItemList& aItems,
+ nsContainerFrame* aParentFrame,
+ bool aParentIsWrapperAnonBox,
+ nsFrameList& aFrameList);
+ void ConstructFramesFromItem(nsFrameConstructorState& aState,
+ FCItemIterator& aItem,
+ nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList);
+ static bool AtLineBoundary(FCItemIterator& aIter);
+
+ nsresult GetAnonymousContent(
+ nsIContent* aParent, nsIFrame* aParentFrame,
+ nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonContent);
+
+ // MathML Mod - RBS
+ /**
+ * Takes the frames in aBlockList and wraps them in a new anonymous block
+ * frame whose content is aContent and whose parent will be aParentFrame.
+ * The anonymous block is added to aNewList and aBlockList is cleared.
+ */
+ void FlushAccumulatedBlock(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsFrameList& aBlockList, nsFrameList& aNewList);
+
+ // Function to find FrameConstructionData for an element. Will return
+ // null if the element is not MathML.
+ static const FrameConstructionData* FindMathMLData(const Element&,
+ ComputedStyle&);
+
+ // Function to find FrameConstructionData for an element. Will return
+ // null if the element is not XUL.
+ static const FrameConstructionData* FindXULTagData(const Element&,
+ ComputedStyle&);
+ // XUL data-finding helper functions and structures
+ static const FrameConstructionData* FindPopupGroupData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindXULButtonData(const Element&,
+ ComputedStyle&);
+ static const FrameConstructionData* FindXULLabelOrDescriptionData(
+ const Element&, ComputedStyle&);
+#ifdef XP_MACOSX
+ static const FrameConstructionData* FindXULMenubarData(const Element&,
+ ComputedStyle&);
+#endif /* XP_MACOSX */
+
+ /**
+ * Constructs an outer frame, an anonymous child that wraps its real
+ * children, and its descendant frames. This is used by both
+ * ConstructOuterSVG and ConstructMarker, which both want an anonymous block
+ * child for their children to go in to.
+ */
+ nsContainerFrame* ConstructFrameWithAnonymousChild(
+ nsFrameConstructorState& aState, FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame, nsFrameList& aFrameList,
+ ContainerFrameCreationFunc aConstructor,
+ ContainerFrameCreationFunc aInnerConstructor,
+ mozilla::PseudoStyleType aInnerPseudo, bool aCandidateRootFrame);
+
+ /**
+ * Construct an SVGOuterSVGFrame.
+ */
+ nsIFrame* ConstructOuterSVG(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * Construct an SVGMarkerFrame.
+ */
+ nsIFrame* ConstructMarker(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ static const FrameConstructionData* FindSVGData(const Element&,
+ nsIFrame* aParentFrame,
+ bool aIsWithinSVGText,
+ bool aAllowsTextPathChild,
+ ComputedStyle&);
+
+ // Not static because it does PropagateScrollToViewport. If this
+ // changes, make this static.
+ const FrameConstructionData* FindDisplayData(const nsStyleDisplay&,
+ const Element&);
+
+ /**
+ * Construct a scrollable block frame
+ */
+ nsIFrame* ConstructScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * Construct a non-scrollable block frame
+ */
+ nsIFrame* ConstructNonScrollableBlock(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * This adds FrameConstructionItem objects to aItemsToConstruct for the
+ * anonymous content returned by an nsIAnonymousContentCreator::
+ * CreateAnonymousContent implementation.
+ * This includes an AutoFrameConstructionPageName argument as it is always
+ * the caller's responsibility to handle page-name tracking before calling
+ * this function.
+ */
+ void AddFCItemsForAnonymousContent(
+ nsFrameConstructorState& aState, nsContainerFrame* aFrame,
+ const nsTArray<nsIAnonymousContentCreator::ContentInfo>& aAnonymousItems,
+ FrameConstructionItemList& aItemsToConstruct,
+ const AutoFrameConstructionPageName& aUnusedPageNameTracker);
+
+ /**
+ * Construct the frames for the children of aContent. "children" is defined
+ * as "whatever FlattenedChildIterator returns for aContent". This means
+ * we're basically operating on children in the "flattened tree":
+ *
+ * https://drafts.csswg.org/css-scoping/#flat-tree
+ *
+ * This method will also handle constructing ::before, ::after,
+ * ::first-letter, and ::first-line frames, as needed and if allowed.
+ *
+ * If the parent is a float containing block, this method will handle pushing
+ * it as the float containing block in aState (so there's no need for callers
+ * to push it themselves).
+ *
+ * @param aState the frame construction state
+ * @param aContent the content node whose children need frames
+ * @param aComputedStyle the style for aContent
+ * @param aParentFrame the frame to use as the parent frame for the new
+ * in-flow kids. Note that this must be its own content insertion frame, but
+ * need not be be the primary frame for aContent. This frame will be
+ * pushed as the float containing block, as needed. aFrame is also
+ * used to find the parent style for the kids' style
+ * (not necessary aFrame's style).
+ * @param aCanHaveGeneratedContent Whether to allow :before and
+ * :after styles on the parent.
+ * @param aFrameList the list in which we should place the in-flow children
+ * @param aAllowBlockStyles Whether to allow first-letter and first-line
+ * styles on the parent.
+ * @param aPossiblyLeafFrame if non-null, this should be used for the isLeaf
+ * test and the anonymous content creation. If null, aFrame will be
+ * used.
+ */
+ void ProcessChildren(nsFrameConstructorState& aState, nsIContent* aContent,
+ ComputedStyle* aComputedStyle,
+ nsContainerFrame* aParentFrame,
+ const bool aCanHaveGeneratedContent,
+ nsFrameList& aFrameList, const bool aAllowBlockStyles,
+ nsIFrame* aPossiblyLeafFrame = nullptr);
+
+ /**
+ * These two functions are used when we start frame creation from a non-root
+ * element. They should recreate the same state that we would have
+ * arrived at if we had built frames from the root frame to aFrame.
+ * Therefore, any calls to PushFloatContainingBlock and
+ * PushAbsoluteContainingBlock during frame construction should get
+ * corresponding logic in these functions.
+ */
+ public:
+ enum ContainingBlockType { ABS_POS, FIXED_POS };
+ nsContainerFrame* GetAbsoluteContainingBlock(nsIFrame* aFrame,
+ ContainingBlockType aType);
+ nsContainerFrame* GetFloatContainingBlock(nsIFrame* aFrame);
+
+ private:
+ // Build a scroll frame:
+ // Calls BeginBuildingScrollFrame, InitAndRestoreFrame, and then
+ // FinishBuildingScrollFrame.
+ // @param aNewFrame the created scrollframe --- output only
+ // @param aParentFrame the geometric parent that the scrollframe will have.
+ void BuildScrollFrame(nsFrameConstructorState& aState, nsIContent* aContent,
+ ComputedStyle* aContentStyle, nsIFrame* aScrolledFrame,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame*& aNewFrame);
+
+ // Builds the initial ScrollFrame
+ already_AddRefed<ComputedStyle> BeginBuildingScrollFrame(
+ nsFrameConstructorState& aState, nsIContent* aContent,
+ ComputedStyle* aContentStyle, nsContainerFrame* aParentFrame,
+ mozilla::PseudoStyleType aScrolledPseudo, bool aIsRoot,
+ nsContainerFrame*& aNewFrame);
+
+ // Completes the building of the scrollframe:
+ // Creates a view for the scrolledframe and makes it the child of the
+ // scrollframe.
+ void FinishBuildingScrollFrame(nsContainerFrame* aScrollFrame,
+ nsIFrame* aScrolledFrame);
+
+ void InitializeListboxSelect(nsFrameConstructorState& aState,
+ nsContainerFrame* aScrollFrame,
+ nsContainerFrame* aScrolledFrame,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ ComputedStyle* aComputedStyle,
+ nsFrameList& aFrameList);
+
+ /**
+ * Recreate frames for aContent.
+ * @param aContent the content to recreate frames for
+ * @param aFlags normally you want to pass REMOVE_FOR_RECONSTRUCTION here
+ */
+ void RecreateFramesForContent(nsIContent* aContent,
+ InsertionKind aInsertionKind);
+
+ /**
+ * Handles change of rowspan and colspan attributes on table cells.
+ */
+ void UpdateTableCellSpans(nsIContent* aContent);
+
+ // If removal of aFrame from the frame tree requires reconstruction of some
+ // containing block (either of aFrame or of its parent) due to {ib} splits or
+ // table pseudo-frames, recreate the relevant frame subtree. The return value
+ // indicates whether this happened. aFrame must be the result of a
+ // GetPrimaryFrame() call on a content node (which means its parent is also
+ // not null).
+ bool MaybeRecreateContainerForFrameRemoval(nsIFrame* aFrame);
+
+ nsIFrame* CreateContinuingOuterTableFrame(nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ ComputedStyle* aComputedStyle);
+
+ nsIFrame* CreateContinuingTableFrame(nsIFrame* aFrame,
+ nsContainerFrame* aParentFrame,
+ nsIContent* aContent,
+ ComputedStyle* aComputedStyle);
+
+ //----------------------------------------
+
+ // Methods support creating block frames and their children
+
+ already_AddRefed<ComputedStyle> GetFirstLetterStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle);
+
+ already_AddRefed<ComputedStyle> GetFirstLineStyle(
+ nsIContent* aContent, ComputedStyle* aComputedStyle);
+
+ bool ShouldHaveFirstLetterStyle(nsIContent* aContent,
+ ComputedStyle* aComputedStyle);
+
+ // Check whether a given block has first-letter style. Make sure to
+ // only pass in blocks! And don't pass in null either.
+ bool HasFirstLetterStyle(nsIFrame* aBlockFrame);
+
+ bool ShouldHaveFirstLineStyle(nsIContent* aContent,
+ ComputedStyle* aComputedStyle);
+
+ void ShouldHaveSpecialBlockStyle(nsIContent* aContent,
+ ComputedStyle* aComputedStyle,
+ bool* aHaveFirstLetterStyle,
+ bool* aHaveFirstLineStyle);
+
+ // |aContentParentFrame| should be null if it's really the same as
+ // |aParentFrame|.
+ // @param aFrameList where we want to put the block in case it's in-flow.
+ // @param aNewFrame an in/out parameter. On input it is the block to be
+ // constructed. On output it is reset to the outermost
+ // frame constructed (e.g. if we need to wrap the block in an
+ // nsColumnSetFrame.
+ // @param aParentFrame is the desired parent for the (possibly wrapped)
+ // block
+ // @param aContentParent is the parent the block would have if it
+ // were in-flow
+ // @param aPositionedFrameForAbsPosContainer if non-null, then the new
+ // block should be an abs-pos container and aPositionedFrameForAbsPosContainer
+ // is the frame whose style is making this block an abs-pos container.
+ void ConstructBlock(nsFrameConstructorState& aState, nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame* aContentParentFrame,
+ ComputedStyle* aComputedStyle,
+ nsContainerFrame** aNewFrame, nsFrameList& aFrameList,
+ nsIFrame* aPositionedFrameForAbsPosContainer);
+
+ // Build the initial column hierarchy around aColumnContent. This function
+ // should be called before constructing aColumnContent's children.
+ //
+ // Before calling FinishBuildingColumns(), we need to create column-span
+ // siblings for aColumnContent's children. Caller can use helpers
+ // MayNeedToCreateColumnSpanSiblings() and CreateColumnSpanSiblings() to
+ // check whether column-span siblings might need to be created and to do
+ // the actual work of creating them if they're needed.
+ //
+ // @param aColumnContent the block that we're wrapping in a ColumnSet. On
+ // entry to this function it has aComputedStyle as its style. After
+ // this function returns, aColumnContent has a ::-moz-column-content
+ // anonymous box style.
+ // @param aParentFrame the parent frame we want to use for the
+ // ColumnSetWrapperFrame (which would have been the parent of
+ // aColumnContent if we were not creating a column hierarchy).
+ // @param aContent is the content of the aColumnContent.
+ // @return the outermost ColumnSetWrapperFrame.
+ nsBlockFrame* BeginBuildingColumns(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aParentFrame,
+ nsContainerFrame* aColumnContent,
+ ComputedStyle* aComputedStyle);
+
+ // Complete building the column hierarchy by first wrapping each
+ // non-column-span child in aChildList in a ColumnSetFrame (skipping
+ // column-span children), and reparenting them to have aColumnSetWrapper
+ // as their parent.
+ //
+ // @param aColumnSetWrapper is the frame returned by
+ // BeginBuildingColumns(), and is the grandparent of aColumnContent.
+ // @param aColumnContent is the block frame passed into
+ // BeginBuildingColumns()
+ // @param aColumnContentSiblings contains the aColumnContent's siblings, which
+ // are the column spanners and aColumnContent's continuations returned
+ // by CreateColumnSpanSiblings(). It'll become empty after this call.
+ void FinishBuildingColumns(nsFrameConstructorState& aState,
+ nsContainerFrame* aColumnSetWrapper,
+ nsContainerFrame* aColumnContent,
+ nsFrameList& aColumnContentSiblings);
+
+ // Return whether aBlockFrame's children in aChildList, which might
+ // contain column-span, may need to be wrapped in
+ // ::moz-column-span-wrapper and promoted as aBlockFrame's siblings.
+ //
+ // @param aBlockFrame is the parent of the frames in aChildList.
+ //
+ // Note: This a check without actually looking into each frame in the
+ // child list, so it may return false positive.
+ bool MayNeedToCreateColumnSpanSiblings(nsContainerFrame* aBlockFrame,
+ const nsFrameList& aChildList);
+
+ // Wrap consecutive runs of column-span kids and runs of non-column-span
+ // kids in blocks for aInitialBlock's children.
+ //
+ // @param aInitialBlock is the parent of those frames in aChildList.
+ // @param aChildList must begin with a column-span kid. It becomes empty
+ // after this call.
+ // @param aPositionedFrame if non-null, it's the frame whose style is making
+ // aInitialBlock an abs-pos container.
+ //
+ // Return those wrapping blocks in nsFrameList.
+ nsFrameList CreateColumnSpanSiblings(nsFrameConstructorState& aState,
+ nsContainerFrame* aInitialBlock,
+ nsFrameList& aChildList,
+ nsIFrame* aPositionedFrame);
+
+ // Reconstruct the multi-column containing block of aParentFrame when we want
+ // to insert aFrameList into aParentFrame immediately after aPrevSibling but
+ // cannot fix the frame tree because aFrameList contains some column-spans.
+ //
+ // Note: This method is intended to be called as a helper in ContentAppended()
+ // and ContentRangeInserted(). It assumes aState was set up locally and wasn't
+ // used to construct any ancestors of aParentFrame in aFrameList.
+ //
+ // @param aParentFrame the to-be parent frame for aFrameList.
+ // @param aFrameList the frames to be inserted. It will be cleared if we need
+ // reconstruction.
+ // @param aPrevSibling the position where the frames in aFrameList are going
+ // to be inserted. Nullptr means aFrameList is being inserted at
+ // the beginning.
+ // @return true if the multi-column containing block of aParentFrame is
+ // reconstructed; false otherwise.
+ bool MaybeRecreateForColumnSpan(nsFrameConstructorState& aState,
+ nsContainerFrame* aParentFrame,
+ nsFrameList& aFrameList,
+ nsIFrame* aPrevSibling);
+
+ nsIFrame* ConstructInline(nsFrameConstructorState& aState,
+ FrameConstructionItem& aItem,
+ nsContainerFrame* aParentFrame,
+ const nsStyleDisplay* aDisplay,
+ nsFrameList& aFrameList);
+
+ /**
+ * Create any additional {ib} siblings needed to contain aChildList and put
+ * them in aSiblings.
+ *
+ * @param aState the frame constructor state
+ * @param aInitialInline is an already-existing inline frame that will be
+ * part of this {ib} split and come before everything
+ * in aSiblings.
+ * @param aIsPositioned true if aInitialInline is positioned.
+ * @param aChildList is a child list starting with a block; this method
+ * assumes that the inline has already taken all the
+ * children it wants. When the method returns aChildList
+ * will be empty.
+ * @param aSiblings the nsFrameList to put the newly-created siblings into.
+ *
+ * This method is responsible for making any SetFrameIsIBSplit calls that are
+ * needed.
+ */
+ void CreateIBSiblings(nsFrameConstructorState& aState,
+ nsContainerFrame* aInitialInline, bool aIsPositioned,
+ nsFrameList& aChildList, nsFrameList& aSiblings);
+
+ /**
+ * For an inline aParentItem, construct its list of child
+ * FrameConstructionItems and set its mIsAllInline flag appropriately.
+ */
+ void BuildInlineChildItems(nsFrameConstructorState& aState,
+ FrameConstructionItem& aParentItem,
+ bool aItemIsWithinSVGText,
+ bool aItemAllowsTextPathChild);
+
+ // Determine whether we need to wipe out aFrame (the insertion parent) and
+ // rebuild the entire subtree when we insert or append new content under
+ // aFrame.
+ //
+ // This is similar to WipeContainingBlock(), but is called before constructing
+ // any frame construction items. Any container frames which need reframing
+ // regardless of the content inserted or appended can add a check in this
+ // method.
+ //
+ // @return true if we reconstructed the insertion parent frame; false
+ // otherwise
+ bool WipeInsertionParent(nsContainerFrame* aFrame);
+
+ // Determine whether we need to wipe out what we just did and start over
+ // because we're doing something like adding block kids to an inline frame
+ // (and therefore need an {ib} split). aPrevSibling must be correct, even in
+ // aIsAppend cases. Passing aIsAppend false even when an append is happening
+ // is ok in terms of correctness, but can lead to unnecessary reframing. If
+ // aIsAppend is true, then the caller MUST call
+ // nsCSSFrameConstructor::AppendFramesToParent (as opposed to
+ // nsFrameManager::InsertFrames directly) to add the new frames.
+ // @return true if we reconstructed the containing block, false
+ // otherwise
+ bool WipeContainingBlock(nsFrameConstructorState& aState,
+ nsIFrame* aContainingBlock, nsIFrame* aFrame,
+ FrameConstructionItemList& aItems, bool aIsAppend,
+ nsIFrame* aPrevSibling);
+
+ void ReframeContainingBlock(nsIFrame* aFrame);
+
+ //----------------------------------------
+
+ // Methods support :first-letter style
+
+ nsFirstLetterFrame* CreateFloatingLetterFrame(
+ nsFrameConstructorState& aState, mozilla::dom::Text* aTextContent,
+ nsIFrame* aTextFrame, nsContainerFrame* aParentFrame,
+ ComputedStyle* aParentStyle, ComputedStyle* aComputedStyle,
+ nsFrameList& aResult);
+
+ void CreateLetterFrame(nsContainerFrame* aBlockFrame,
+ nsContainerFrame* aBlockContinuation,
+ mozilla::dom::Text* aTextContent,
+ nsContainerFrame* aParentFrame, nsFrameList& aResult);
+
+ void WrapFramesInFirstLetterFrame(nsContainerFrame* aBlockFrame,
+ nsFrameList& aBlockFrames);
+
+ /**
+ * Looks in the block aBlockFrame for a text frame that contains the
+ * first-letter of the block and creates the necessary first-letter frames
+ * and returns them in aLetterFrames.
+ *
+ * @param aBlockFrame the (first-continuation of) the block we are creating a
+ * first-letter frame for
+ * @param aBlockContinuation the current continuation of the block that we
+ * are looking in for a textframe with suitable
+ * contents for first-letter
+ * @param aParentFrame the current frame whose children we are looking at for
+ * a suitable first-letter textframe
+ * @param aParentFrameList the first child of aParentFrame
+ * @param aModifiedParent returns the parent of the textframe that contains
+ * the first-letter
+ * @param aTextFrame returns the textframe that had the first-letter
+ * @param aPrevFrame returns the previous sibling of aTextFrame
+ * @param aLetterFrames returns the frames that were created
+ */
+ void WrapFramesInFirstLetterFrame(
+ nsContainerFrame* aBlockFrame, nsContainerFrame* aBlockContinuation,
+ nsContainerFrame* aParentFrame, nsIFrame* aParentFrameList,
+ nsContainerFrame** aModifiedParent, nsIFrame** aTextFrame,
+ nsIFrame** aPrevFrame, nsFrameList& aLetterFrames, bool* aStopLooking);
+
+ void RecoverLetterFrames(nsContainerFrame* aBlockFrame);
+
+ void RemoveLetterFrames(PresShell* aPresShell, nsContainerFrame* aBlockFrame);
+
+ // Recursive helper for RemoveLetterFrames
+ void RemoveFirstLetterFrames(PresShell* aPresShell, nsContainerFrame* aFrame,
+ nsContainerFrame* aBlockFrame,
+ bool* aStopLooking);
+
+ // Special remove method for those pesky floating first-letter frames
+ void RemoveFloatingFirstLetterFrames(PresShell* aPresShell,
+ nsIFrame* aBlockFrame);
+
+ // Capture state for the frame tree rooted at the frame associated with the
+ // content object, aContent
+ void CaptureStateForFramesOf(nsIContent* aContent,
+ nsILayoutHistoryState* aHistoryState);
+
+ //----------------------------------------
+
+ // Methods support :first-line style
+
+ // This method chops the initial inline-outside frames out of aFrameList.
+ // If aLineFrame is non-null, it appends them to that frame. Otherwise, it
+ // creates a new line frame, sets the inline frames as its initial child
+ // list, and inserts that line frame at the front of what's left of
+ // aFrameList. In both cases, the kids are reparented to the line frame.
+ // After this call, aFrameList holds the frames that need to become kids of
+ // the block (possibly including line frames).
+ void WrapFramesInFirstLineFrame(nsFrameConstructorState& aState,
+ nsIContent* aBlockContent,
+ nsContainerFrame* aBlockFrame,
+ nsFirstLineFrame* aLineFrame,
+ nsFrameList& aFrameList);
+
+ // Handle the case when a block with first-line style is appended to (by
+ // possibly calling WrapFramesInFirstLineFrame as needed).
+ void AppendFirstLineFrames(nsFrameConstructorState& aState,
+ nsIContent* aContent,
+ nsContainerFrame* aBlockFrame,
+ nsFrameList& aFrameList);
+
+ /**
+ * When aFrameList is being inserted into aParentFrame, and aParentFrame has
+ * pseudo-element-affected styles, it's possible that we're inserting under a
+ * ::first-line frame. In that case, with servo's style system, the styles we
+ * resolved for aFrameList are wrong (they don't take ::first-line into
+ * account), and we should fix them up, which is what this method does.
+ *
+ * This method does not mutate aFrameList.
+ */
+ void CheckForFirstLineInsertion(nsIFrame* aParentFrame,
+ nsFrameList& aFrameList);
+
+ /**
+ * Find the next frame for appending to a given insertion point.
+ *
+ * We're appending, so this is almost always null, except for a few edge
+ * cases.
+ */
+ nsIFrame* FindNextSiblingForAppend(const InsertionPoint&);
+
+ // The direction in which we should look for siblings.
+ enum class SiblingDirection {
+ Forward,
+ Backward,
+ };
+
+ /**
+ * Find the frame for the content immediately next to the one aIter points to,
+ * in the direction SiblingDirection indicates, following continuations if
+ * necessary.
+ *
+ * aIter is passed by const reference on purpose, so as not to modify the
+ * caller's iterator.
+ *
+ * @param aIter should be positioned such that aIter.GetPreviousChild()
+ * is the first content to search for frames
+ * @param aTargetContentDisplay the CSS display enum for the content aIter
+ * points to if already known. It will be filled in if needed.
+ */
+ template <SiblingDirection>
+ nsIFrame* FindSibling(
+ const mozilla::dom::FlattenedChildIterator& aIter,
+ mozilla::Maybe<mozilla::StyleDisplay>& aTargetContentDisplay);
+
+ // Helper for the implementation of FindSibling.
+ //
+ // Beware that this function does mutate the iterator.
+ template <SiblingDirection>
+ nsIFrame* FindSiblingInternal(
+ mozilla::dom::FlattenedChildIterator&, nsIContent* aTargetContent,
+ mozilla::Maybe<mozilla::StyleDisplay>& aTargetContentDisplay);
+
+ // An alias of FindSibling<SiblingDirection::Forward>.
+ nsIFrame* FindNextSibling(
+ const mozilla::dom::FlattenedChildIterator& aIter,
+ mozilla::Maybe<mozilla::StyleDisplay>& aTargetContentDisplay);
+ // An alias of FindSibling<SiblingDirection::Backwards>.
+ nsIFrame* FindPreviousSibling(
+ const mozilla::dom::FlattenedChildIterator& aIter,
+ mozilla::Maybe<mozilla::StyleDisplay>& aTargetContentDisplay);
+
+ // Given a potential first-continuation sibling frame for aTargetContent,
+ // verify that it is an actual valid sibling for it, and return the
+ // appropriate continuation the new frame for aTargetContent should be
+ // inserted next to.
+ nsIFrame* AdjustSiblingFrame(
+ nsIFrame* aSibling, nsIContent* aTargetContent,
+ mozilla::Maybe<mozilla::StyleDisplay>& aTargetContentDisplay,
+ SiblingDirection aDirection);
+
+ // Find the right previous sibling for an insertion. This also updates the
+ // parent frame to point to the correct continuation of the parent frame to
+ // use, and returns whether this insertion is to be treated as an append.
+ // aChild is the child being inserted.
+ // aIsRangeInsertSafe returns whether it is safe to do a range insert with
+ // aChild being the first child in the range. It is the callers'
+ // responsibility to check whether a range insert is safe with regards to
+ // fieldsets.
+ // The skip parameters are used to ignore a range of children when looking
+ // for a sibling. All nodes starting from aStartSkipChild and up to but not
+ // including aEndSkipChild will be skipped over when looking for sibling
+ // frames. Skipping a range can deal with shadow DOM, but not when there are
+ // multiple insertion points.
+ nsIFrame* GetInsertionPrevSibling(InsertionPoint* aInsertion, // inout
+ nsIContent* aChild, bool* aIsAppend,
+ bool* aIsRangeInsertSafe,
+ nsIContent* aStartSkipChild = nullptr,
+ nsIContent* aEndSkipChild = nullptr);
+
+ // see if aContent and aSibling are legitimate siblings due to restrictions
+ // imposed by table columns
+ // XXXbz this code is generally wrong, since the frame for aContent
+ // may be constructed based on tag, not based on aDisplay!
+ bool IsValidSibling(nsIFrame* aSibling, nsIContent* aContent,
+ mozilla::Maybe<mozilla::StyleDisplay>& aDisplay);
+
+ void QuotesDirty();
+ void CountersDirty();
+
+ void ConstructAnonymousContentForCanvas(nsFrameConstructorState& aState,
+ nsContainerFrame* aFrame,
+ nsIContent* aDocElement,
+ nsFrameList&);
+
+ public:
+ friend class nsFrameConstructorState;
+
+ private:
+ // For allocating FrameConstructionItems from the mFCItemPool arena.
+ friend struct FrameConstructionItem;
+ void* AllocateFCItem();
+ void FreeFCItem(FrameConstructionItem*);
+
+ mozilla::dom::Document* mDocument; // Weak ref
+
+ // See the comment at the start of ConstructRootFrame for more details
+ // about the following frames.
+
+ // This is just the outermost frame for the root element.
+ nsContainerFrame* mRootElementFrame = nullptr;
+ // This is the frame for the root element that has no pseudo-element style.
+ nsIFrame* mRootElementStyleFrame = nullptr;
+ // This is the containing block that contains the root element ---
+ // the real "initial containing block" according to CSS 2.1.
+ nsCanvasFrame* mDocElementContainingBlock = nullptr;
+ // This is usually mDocElementContainingBlock, except when printing, where it
+ // is the canvas frame that is under all the printed pages.
+ nsCanvasFrame* mCanvasFrame = nullptr;
+ nsPageSequenceFrame* mPageSequenceFrame = nullptr;
+
+ // FrameConstructionItem arena + list of freed items available for re-use.
+ mozilla::ArenaAllocator<4096, 8> mFCItemPool;
+
+ // This indicates what page name to use for the next nsPageContentFrame.
+ // Set when CSS named pages cause a breakpoint.
+ // This does not apply to the first page content frame, which has its name
+ // set by nsPageContentFrame::EnsurePageName() during first reflow.
+ RefPtr<const nsAtom> mNextPageContentFramePageName;
+
+ struct FreeFCItemLink {
+ FreeFCItemLink* mNext;
+ };
+ FreeFCItemLink* mFirstFreeFCItem;
+ size_t mFCItemsInUse;
+
+ mozilla::ContainStyleScopeManager mContainStyleScopeManager;
+
+ // Current ProcessChildren depth.
+ uint16_t mCurrentDepth;
+ bool mQuotesDirty : 1;
+ bool mCountersDirty : 1;
+ bool mAlwaysCreateFramesForIgnorableWhitespace : 1;
+
+ // The layout state from our history entry (to restore scroll positions and
+ // such from history), or a new one if there was none (so we can store scroll
+ // positions and such during reframe).
+ //
+ // FIXME(bug 1397239): This can leak some state sometimes for the lifetime of
+ // the frame constructor, which is not great.
+ nsCOMPtr<nsILayoutHistoryState> mFrameTreeState;
+};
+
+#endif /* nsCSSFrameConstructor_h___ */
diff --git a/layout/base/nsCaret.cpp b/layout/base/nsCaret.cpp
new file mode 100644
index 0000000000..d66d55d6bb
--- /dev/null
+++ b/layout/base/nsCaret.cpp
@@ -0,0 +1,766 @@
+/* -*- 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/. */
+
+/* the caret is the text cursor used, e.g., when editing */
+
+#include "nsCaret.h"
+
+#include <algorithm>
+
+#include "gfxUtils.h"
+#include "mozilla/CaretAssociationHint.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/StaticPrefs_bidi.h"
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsITimer.h"
+#include "nsFrameSelection.h"
+#include "nsIFrame.h"
+#include "nsIScrollableFrame.h"
+#include "nsIContent.h"
+#include "nsIFrameInlines.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "nsBlockFrame.h"
+#include "nsISelectionController.h"
+#include "nsTextFrame.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsTextFragment.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/dom/Selection.h"
+#include "nsIBidiKeyboard.h"
+#include "nsContentUtils.h"
+#include "SelectionMovementUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+using BidiEmbeddingLevel = mozilla::intl::BidiEmbeddingLevel;
+
+// The bidi indicator hangs off the caret to one side, to show which
+// direction the typing is in. It needs to be at least 2x2 to avoid looking
+// like an insignificant dot
+static const int32_t kMinBidiIndicatorPixels = 2;
+
+nsCaret::nsCaret()
+ : mOverrideOffset(0),
+ mBlinkCount(-1),
+ mBlinkRate(0),
+ mHideCount(0),
+ mIsBlinkOn(false),
+ mVisible(false),
+ mReadOnly(false),
+ mShowDuringSelection(false),
+ mIgnoreUserModify(true) {}
+
+nsCaret::~nsCaret() { StopBlinking(); }
+
+nsresult nsCaret::Init(PresShell* aPresShell) {
+ NS_ENSURE_ARG(aPresShell);
+
+ mPresShell =
+ do_GetWeakReference(aPresShell); // the presshell owns us, so no addref
+ NS_ASSERTION(mPresShell, "Hey, pres shell should support weak refs");
+
+ mShowDuringSelection =
+ LookAndFeel::GetInt(LookAndFeel::IntID::ShowCaretDuringSelection,
+ mShowDuringSelection ? 1 : 0) != 0;
+
+ RefPtr<Selection> selection =
+ aPresShell->GetSelection(nsISelectionController::SELECTION_NORMAL);
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ selection->AddSelectionListener(this);
+ mDomSelectionWeak = selection;
+
+ return NS_OK;
+}
+
+static bool DrawCJKCaret(nsIFrame* aFrame, int32_t aOffset) {
+ nsIContent* content = aFrame->GetContent();
+ const nsTextFragment* frag = content->GetText();
+ if (!frag) {
+ return false;
+ }
+ if (aOffset < 0 || static_cast<uint32_t>(aOffset) >= frag->GetLength()) {
+ return false;
+ }
+ const char16_t ch = frag->CharAt(AssertedCast<uint32_t>(aOffset));
+ return 0x2e80 <= ch && ch <= 0xd7ff;
+}
+
+nsCaret::Metrics nsCaret::ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
+ nscoord aCaretHeight) {
+ // Compute nominal sizes in appunits
+ nscoord caretWidth =
+ (aCaretHeight *
+ LookAndFeel::GetFloat(LookAndFeel::FloatID::CaretAspectRatio, 0.0f)) +
+ nsPresContext::CSSPixelsToAppUnits(
+ LookAndFeel::GetInt(LookAndFeel::IntID::CaretWidth, 1));
+
+ if (DrawCJKCaret(aFrame, aOffset)) {
+ caretWidth += nsPresContext::CSSPixelsToAppUnits(1);
+ }
+ nscoord bidiIndicatorSize =
+ nsPresContext::CSSPixelsToAppUnits(kMinBidiIndicatorPixels);
+ bidiIndicatorSize = std::max(caretWidth, bidiIndicatorSize);
+
+ // Round them to device pixels. Always round down, except that anything
+ // between 0 and 1 goes up to 1 so we don't let the caret disappear.
+ int32_t tpp = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Metrics result;
+ result.mCaretWidth = NS_ROUND_BORDER_TO_PIXELS(caretWidth, tpp);
+ result.mBidiIndicatorSize = NS_ROUND_BORDER_TO_PIXELS(bidiIndicatorSize, tpp);
+ return result;
+}
+
+void nsCaret::Terminate() {
+ // this doesn't erase the caret if it's drawn. Should it? We might not have
+ // a good drawing environment during teardown.
+
+ StopBlinking();
+ mBlinkTimer = nullptr;
+
+ // unregiser ourselves as a selection listener
+ if (mDomSelectionWeak) {
+ mDomSelectionWeak->RemoveSelectionListener(this);
+ }
+ mDomSelectionWeak = nullptr;
+ mPresShell = nullptr;
+
+ mOverrideContent = nullptr;
+}
+
+NS_IMPL_ISUPPORTS(nsCaret, nsISelectionListener)
+
+Selection* nsCaret::GetSelection() { return mDomSelectionWeak; }
+
+void nsCaret::SetSelection(Selection* aDOMSel) {
+ MOZ_ASSERT(aDOMSel);
+ mDomSelectionWeak = aDOMSel;
+ ResetBlinking();
+ SchedulePaint(aDOMSel);
+}
+
+void nsCaret::SetVisible(bool inMakeVisible) {
+ mVisible = inMakeVisible;
+ mIgnoreUserModify = mVisible;
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::AddForceHide() {
+ MOZ_ASSERT(mHideCount < UINT32_MAX);
+ if (++mHideCount > 1) {
+ return;
+ }
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::RemoveForceHide() {
+ if (!mHideCount || --mHideCount) {
+ return;
+ }
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::SetCaretReadOnly(bool inMakeReadonly) {
+ mReadOnly = inMakeReadonly;
+ ResetBlinking();
+ SchedulePaint();
+}
+
+// Clamp the inline-position to be within our closest scroll frame and any
+// ancestor clips if any. If we don't, then it clips us, and we don't appear at
+// all. See bug 335560 and bug 1539720.
+static nsPoint AdjustRectForClipping(const nsRect& aRect, nsIFrame* aFrame,
+ bool aVertical) {
+ nsRect rectRelativeToClip = aRect;
+ nsIScrollableFrame* sf = nullptr;
+ nsIFrame* scrollFrame = nullptr;
+ for (nsIFrame* current = aFrame; current; current = current->GetParent()) {
+ if ((sf = do_QueryFrame(current))) {
+ scrollFrame = current;
+ break;
+ }
+ if (current->IsTransformed()) {
+ // We don't account for transforms in rectRelativeToCurrent, so stop
+ // adjusting here.
+ break;
+ }
+ rectRelativeToClip += current->GetPosition();
+ }
+
+ if (!sf) {
+ return {};
+ }
+
+ nsRect clipRect = sf->GetScrollPortRect();
+ {
+ const auto& disp = *scrollFrame->StyleDisplay();
+ if (disp.mOverflowClipBoxBlock == StyleOverflowClipBox::ContentBox ||
+ disp.mOverflowClipBoxInline == StyleOverflowClipBox::ContentBox) {
+ const WritingMode wm = scrollFrame->GetWritingMode();
+ const bool cbH = (wm.IsVertical() ? disp.mOverflowClipBoxBlock
+ : disp.mOverflowClipBoxInline) ==
+ StyleOverflowClipBox::ContentBox;
+ const bool cbV = (wm.IsVertical() ? disp.mOverflowClipBoxInline
+ : disp.mOverflowClipBoxBlock) ==
+ StyleOverflowClipBox::ContentBox;
+ nsMargin padding = scrollFrame->GetUsedPadding();
+ if (!cbH) {
+ padding.left = padding.right = 0;
+ }
+ if (!cbV) {
+ padding.top = padding.bottom = 0;
+ }
+ clipRect.Deflate(padding);
+ }
+ }
+ nsPoint offset;
+ // Now see if the caret extends beyond the view's bounds. If it does, then
+ // snap it back, put it as close to the edge as it can.
+ if (aVertical) {
+ nscoord overflow = rectRelativeToClip.YMost() - clipRect.YMost();
+ if (overflow > 0) {
+ offset.y -= overflow;
+ } else {
+ overflow = rectRelativeToClip.y - clipRect.y;
+ if (overflow < 0) {
+ offset.y -= overflow;
+ }
+ }
+ } else {
+ nscoord overflow = rectRelativeToClip.XMost() - clipRect.XMost();
+ if (overflow > 0) {
+ offset.x -= overflow;
+ } else {
+ overflow = rectRelativeToClip.x - clipRect.x;
+ if (overflow < 0) {
+ offset.x -= overflow;
+ }
+ }
+ }
+ return offset;
+}
+
+/* static */
+nsRect nsCaret::GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
+ nscoord* aBidiIndicatorSize) {
+ nsPoint framePos(0, 0);
+ nsRect rect;
+ nsresult rv = aFrame->GetPointFromOffset(aFrameOffset, &framePos);
+ if (NS_FAILED(rv)) {
+ if (aBidiIndicatorSize) {
+ *aBidiIndicatorSize = 0;
+ }
+ return rect;
+ }
+
+ nsIFrame* frame = aFrame->GetContentInsertionFrame();
+ if (!frame) {
+ frame = aFrame;
+ }
+ NS_ASSERTION(!frame->HasAnyStateBits(NS_FRAME_IN_REFLOW),
+ "We should not be in the middle of reflow");
+ WritingMode wm = aFrame->GetWritingMode();
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+ const auto caretBlockAxisMetrics = frame->GetCaretBlockAxisMetrics(wm, *fm);
+ const bool vertical = wm.IsVertical();
+ Metrics caretMetrics =
+ ComputeMetrics(aFrame, aFrameOffset, caretBlockAxisMetrics.mExtent);
+
+ nscoord inlineOffset = 0;
+ if (nsTextFrame* textFrame = do_QueryFrame(aFrame)) {
+ if (gfxTextRun* textRun = textFrame->GetTextRun(nsTextFrame::eInflated)) {
+ // For "upstream" text where the textrun direction is reversed from the
+ // frame's inline-dir we want the caret to be painted before rather than
+ // after its nominal inline position, so we offset by its width.
+ const bool textRunDirIsReverseOfFrame =
+ wm.IsInlineReversed() != textRun->IsInlineReversed();
+ // However, in sideways-lr mode we invert this behavior because this is
+ // the one writing mode where bidi-LTR corresponds to inline-reversed
+ // already, which reverses the desired caret placement behavior.
+ // Note that the following condition is equivalent to:
+ // if ( (!textRun->IsSidewaysLeft() && textRunDirIsReverseOfFrame) ||
+ // (textRun->IsSidewaysLeft() && !textRunDirIsReverseOfFrame) )
+ if (textRunDirIsReverseOfFrame != textRun->IsSidewaysLeft()) {
+ inlineOffset = wm.IsBidiLTR() ? -caretMetrics.mCaretWidth
+ : caretMetrics.mCaretWidth;
+ }
+ }
+ }
+
+ // on RTL frames the right edge of mCaretRect must be equal to framePos
+ if (aFrame->StyleVisibility()->mDirection == StyleDirection::Rtl) {
+ if (vertical) {
+ inlineOffset -= caretMetrics.mCaretWidth;
+ } else {
+ inlineOffset -= caretMetrics.mCaretWidth;
+ }
+ }
+
+ if (vertical) {
+ framePos.x = caretBlockAxisMetrics.mOffset;
+ framePos.y += inlineOffset;
+ } else {
+ framePos.x += inlineOffset;
+ framePos.y = caretBlockAxisMetrics.mOffset;
+ }
+
+ rect = nsRect(framePos, vertical ? nsSize(caretBlockAxisMetrics.mExtent,
+ caretMetrics.mCaretWidth)
+ : nsSize(caretMetrics.mCaretWidth,
+ caretBlockAxisMetrics.mExtent));
+
+ rect.MoveBy(AdjustRectForClipping(rect, aFrame, vertical));
+ if (aBidiIndicatorSize) {
+ *aBidiIndicatorSize = caretMetrics.mBidiIndicatorSize;
+ }
+ return rect;
+}
+
+nsIFrame* nsCaret::GetFrameAndOffset(const Selection* aSelection,
+ nsINode* aOverrideNode,
+ int32_t aOverrideOffset,
+ int32_t* aFrameOffset,
+ nsIFrame** aUnadjustedFrame) {
+ if (aUnadjustedFrame) {
+ *aUnadjustedFrame = nullptr;
+ }
+
+ nsINode* focusNode;
+ int32_t focusOffset;
+
+ if (aOverrideNode) {
+ focusNode = aOverrideNode;
+ focusOffset = aOverrideOffset;
+ } else if (aSelection) {
+ focusNode = aSelection->GetFocusNode();
+ focusOffset = aSelection->FocusOffset();
+ } else {
+ return nullptr;
+ }
+
+ if (!focusNode || !focusNode->IsContent() || !aSelection) {
+ return nullptr;
+ }
+
+ nsIContent* contentNode = focusNode->AsContent();
+ nsFrameSelection* frameSelection = aSelection->GetFrameSelection();
+ BidiEmbeddingLevel bidiLevel = frameSelection->GetCaretBidiLevel();
+ const CaretFrameData result =
+ SelectionMovementUtils::GetCaretFrameForNodeOffset(
+ frameSelection, contentNode, focusOffset, frameSelection->GetHint(),
+ bidiLevel, ForceEditableRegion::No);
+ // FIXME: It's odd to update nsFrameSelection within this method which is
+ // named as a getter.
+ if (result.mFrame) {
+ frameSelection->SetHint(result.mHint);
+ }
+ if (aUnadjustedFrame) {
+ *aUnadjustedFrame = result.mUnadjustedFrame;
+ }
+ if (aFrameOffset) {
+ *aFrameOffset = result.mOffsetInFrameContent;
+ }
+ return result.mFrame;
+}
+
+/* static */
+nsIFrame* nsCaret::GetGeometry(const Selection* aSelection, nsRect* aRect) {
+ int32_t frameOffset;
+ nsIFrame* frame = GetFrameAndOffset(aSelection, nullptr, 0, &frameOffset);
+ if (frame) {
+ *aRect = GetGeometryForFrame(frame, frameOffset, nullptr);
+ }
+ return frame;
+}
+
+[[nodiscard]] static nsIFrame* GetContainingBlockIfNeeded(nsIFrame* aFrame) {
+ if (aFrame->IsBlockOutside() || aFrame->IsBlockFrameOrSubclass()) {
+ return nullptr;
+ }
+ return aFrame->GetContainingBlock();
+}
+
+void nsCaret::SchedulePaint(Selection* aSelection) {
+ Selection* selection;
+ if (aSelection) {
+ selection = aSelection;
+ } else {
+ selection = GetSelection();
+ }
+
+ int32_t frameOffset;
+ nsIFrame* frame = GetFrameAndOffset(selection, mOverrideContent,
+ mOverrideOffset, &frameOffset);
+ if (!frame) {
+ return;
+ }
+
+ if (nsIFrame* cb = GetContainingBlockIfNeeded(frame)) {
+ cb->SchedulePaint();
+ } else {
+ frame->SchedulePaint();
+ }
+}
+
+void nsCaret::SetVisibilityDuringSelection(bool aVisibility) {
+ mShowDuringSelection = aVisibility;
+ SchedulePaint();
+}
+
+void nsCaret::SetCaretPosition(nsINode* aNode, int32_t aOffset) {
+ mOverrideContent = aNode;
+ mOverrideOffset = aOffset;
+
+ ResetBlinking();
+ SchedulePaint();
+}
+
+void nsCaret::CheckSelectionLanguageChange() {
+ if (!StaticPrefs::bidi_browser_ui()) {
+ return;
+ }
+
+ bool isKeyboardRTL = false;
+ nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bidiKeyboard->IsLangRTL(&isKeyboardRTL);
+ }
+ // Call SelectionLanguageChange on every paint. Mostly it will be a noop
+ // but it should be fast anyway. This guarantees we never paint the caret
+ // at the wrong place.
+ Selection* selection = GetSelection();
+ if (selection) {
+ selection->SelectionLanguageChange(isKeyboardRTL);
+ }
+}
+
+// This ensures that the caret is not affected by clips on inlines and so forth.
+[[nodiscard]] static nsIFrame* MapToContainingBlock(nsIFrame* aFrame,
+ nsRect* aCaretRect,
+ nsRect* aHookRect) {
+ nsIFrame* containingBlock = GetContainingBlockIfNeeded(aFrame);
+ if (!containingBlock) {
+ return aFrame;
+ }
+
+ *aCaretRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aCaretRect,
+ containingBlock);
+ *aHookRect = nsLayoutUtils::TransformFrameRectToAncestor(aFrame, *aHookRect,
+ containingBlock);
+ return containingBlock;
+}
+
+nsIFrame* nsCaret::GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
+ nscolor* aCaretColor) {
+ // Return null if we should not be visible.
+ if (!IsVisible() || !mIsBlinkOn) {
+ return nullptr;
+ }
+
+ // Update selection language direction now so the new direction will be
+ // taken into account when computing the caret position below.
+ CheckSelectionLanguageChange();
+
+ int32_t frameOffset;
+ nsIFrame* unadjustedFrame = nullptr;
+ nsIFrame* frame =
+ GetFrameAndOffset(GetSelection(), mOverrideContent, mOverrideOffset,
+ &frameOffset, &unadjustedFrame);
+ MOZ_ASSERT(!!frame == !!unadjustedFrame);
+ if (!frame) {
+ return nullptr;
+ }
+
+ // Now we have a frame, check whether it's appropriate to show the caret here.
+ // Note we need to check the unadjusted frame, otherwise consider the
+ // following case:
+ //
+ // <div contenteditable><span contenteditable=false>Text </span><br>
+ //
+ // Where the selection is targeting the <br>. We want to display the caret,
+ // since the <br> we're focused at is editable, but we do want to paint it at
+ // the adjusted frame offset, so that we can see the collapsed whitespace.
+ const nsStyleUI* ui = unadjustedFrame->StyleUI();
+ if ((!mIgnoreUserModify && ui->UserModify() == StyleUserModify::ReadOnly) ||
+ unadjustedFrame->IsContentDisabled()) {
+ return nullptr;
+ }
+
+ // If the offset falls outside of the frame, then don't paint the caret.
+ if (frame->IsTextFrame()) {
+ auto [startOffset, endOffset] = frame->GetOffsets();
+ if (startOffset > frameOffset || endOffset < frameOffset) {
+ return nullptr;
+ }
+ }
+
+ if (aCaretColor) {
+ *aCaretColor = frame->GetCaretColorAt(frameOffset);
+ }
+
+ ComputeCaretRects(frame, frameOffset, aCaretRect, aHookRect);
+ return MapToContainingBlock(frame, aCaretRect, aHookRect);
+}
+
+nsIFrame* nsCaret::GetPaintGeometry(nsRect* aRect) {
+ nsRect caretRect;
+ nsRect hookRect;
+ nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect);
+ aRect->UnionRect(caretRect, hookRect);
+ return frame;
+}
+
+void nsCaret::PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
+ const nsPoint& aOffset) {
+ nsRect caretRect;
+ nsRect hookRect;
+ nscolor color;
+ nsIFrame* frame = GetPaintGeometry(&caretRect, &hookRect, &color);
+ MOZ_ASSERT(frame == aForFrame, "We're referring different frame");
+
+ if (!frame) {
+ return;
+ }
+
+ int32_t appUnitsPerDevPixel = frame->PresContext()->AppUnitsPerDevPixel();
+ Rect devPxCaretRect = NSRectToSnappedRect(caretRect + aOffset,
+ appUnitsPerDevPixel, aDrawTarget);
+ Rect devPxHookRect =
+ NSRectToSnappedRect(hookRect + aOffset, appUnitsPerDevPixel, aDrawTarget);
+
+ ColorPattern pattern(ToDeviceColor(color));
+ aDrawTarget.FillRect(devPxCaretRect, pattern);
+ if (!hookRect.IsEmpty()) {
+ aDrawTarget.FillRect(devPxHookRect, pattern);
+ }
+}
+
+NS_IMETHODIMP
+nsCaret::NotifySelectionChanged(Document*, Selection* aDomSel, int16_t aReason,
+ int32_t aAmount) {
+ // Note that aDomSel, per the comment below may not be the same as our
+ // selection, but that's OK since if that is the case, it wouldn't have
+ // mattered what IsVisible() returns here, so we just opt for checking
+ // the selection later down below.
+ if ((aReason & nsISelectionListener::MOUSEUP_REASON) ||
+ !IsVisible(aDomSel)) // this wont do
+ return NS_OK;
+
+ // The same caret is shared amongst the document and any text widgets it
+ // may contain. This means that the caret could get notifications from
+ // multiple selections.
+ //
+ // If this notification is for a selection that is not the one the
+ // the caret is currently interested in (mDomSelectionWeak), then there
+ // is nothing to do!
+
+ if (mDomSelectionWeak != aDomSel) return NS_OK;
+
+ ResetBlinking();
+ SchedulePaint(aDomSel);
+
+ return NS_OK;
+}
+
+void nsCaret::ResetBlinking() {
+ using IntID = LookAndFeel::IntID;
+
+ // The default caret blinking rate (in ms of blinking interval)
+ constexpr uint32_t kDefaultBlinkRate = 500;
+ // The default caret blinking count (-1 for "never stop blinking")
+ constexpr int32_t kDefaultBlinkCount = -1;
+
+ mIsBlinkOn = true;
+
+ if (mReadOnly || !mVisible || mHideCount) {
+ StopBlinking();
+ return;
+ }
+
+ auto blinkRate =
+ LookAndFeel::GetInt(IntID::CaretBlinkTime, kDefaultBlinkRate);
+
+ if (blinkRate > 0) {
+ // Make sure to reset the remaining blink count even if the blink rate
+ // hasn't changed.
+ mBlinkCount =
+ LookAndFeel::GetInt(IntID::CaretBlinkCount, kDefaultBlinkCount);
+ }
+
+ if (mBlinkRate == blinkRate) {
+ // If the rate hasn't changed, then there is nothing else to do.
+ return;
+ }
+
+ mBlinkRate = blinkRate;
+
+ if (mBlinkTimer) {
+ mBlinkTimer->Cancel();
+ } else {
+ mBlinkTimer = NS_NewTimer(GetMainThreadSerialEventTarget());
+ if (!mBlinkTimer) {
+ return;
+ }
+ }
+
+ if (blinkRate > 0) {
+ mBlinkTimer->InitWithNamedFuncCallback(CaretBlinkCallback, this, blinkRate,
+ nsITimer::TYPE_REPEATING_SLACK,
+ "nsCaret::CaretBlinkCallback_timer");
+ }
+}
+
+void nsCaret::StopBlinking() {
+ if (mBlinkTimer) {
+ mBlinkTimer->Cancel();
+ mBlinkRate = 0;
+ }
+}
+
+size_t nsCaret::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t total = aMallocSizeOf(this);
+ if (mPresShell) {
+ // We only want the size of the nsWeakReference object, not the PresShell
+ // (since we don't own the PresShell).
+ total += mPresShell->SizeOfOnlyThis(aMallocSizeOf);
+ }
+ if (mBlinkTimer) {
+ total += mBlinkTimer->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ return total;
+}
+
+bool nsCaret::IsMenuPopupHidingCaret() {
+ // Check if there are open popups.
+ nsXULPopupManager* popMgr = nsXULPopupManager::GetInstance();
+ nsTArray<nsIFrame*> popups;
+ popMgr->GetVisiblePopups(popups);
+
+ if (popups.Length() == 0)
+ return false; // No popups, so caret can't be hidden by them.
+
+ // Get the selection focus content, that's where the caret would
+ // go if it was drawn.
+ if (!mDomSelectionWeak) {
+ return true; // No selection/caret to draw.
+ }
+ nsCOMPtr<nsIContent> caretContent =
+ nsIContent::FromNodeOrNull(mDomSelectionWeak->GetFocusNode());
+ if (!caretContent) return true; // No selection/caret to draw.
+
+ // If there's a menu popup open before the popup with
+ // the caret, don't show the caret.
+ for (uint32_t i = 0; i < popups.Length(); i++) {
+ nsMenuPopupFrame* popupFrame = static_cast<nsMenuPopupFrame*>(popups[i]);
+ nsIContent* popupContent = popupFrame->GetContent();
+
+ if (caretContent->IsInclusiveDescendantOf(popupContent)) {
+ // The caret is in this popup. There were no menu popups before this
+ // popup, so don't hide the caret.
+ return false;
+ }
+
+ if (popupFrame->GetPopupType() == widget::PopupType::Menu &&
+ !popupFrame->IsContextMenu()) {
+ // This is an open menu popup. It does not contain the caret (else we'd
+ // have returned above). Even if the caret is in a subsequent popup,
+ // or another document/frame, it should be hidden.
+ return true;
+ }
+ }
+
+ // There are no open menu popups, no need to hide the caret.
+ return false;
+}
+
+void nsCaret::ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
+ nsRect* aCaretRect, nsRect* aHookRect) {
+ NS_ASSERTION(aFrame, "Should have a frame here");
+
+ WritingMode wm = aFrame->GetWritingMode();
+ bool isVertical = wm.IsVertical();
+
+ nscoord bidiIndicatorSize;
+ *aCaretRect = GetGeometryForFrame(aFrame, aFrameOffset, &bidiIndicatorSize);
+
+ // Simon -- make a hook to draw to the left or right of the caret to show
+ // keyboard language direction
+ aHookRect->SetEmpty();
+ if (!StaticPrefs::bidi_browser_ui()) {
+ return;
+ }
+
+ bool isCaretRTL;
+ nsIBidiKeyboard* bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ // if bidiKeyboard->IsLangRTL() fails, there is no way to tell the
+ // keyboard direction, or the user has no right-to-left keyboard
+ // installed, so we never draw the hook.
+ if (bidiKeyboard && NS_SUCCEEDED(bidiKeyboard->IsLangRTL(&isCaretRTL))) {
+ // If keyboard language is RTL, draw the hook on the left; if LTR, to the
+ // right The height of the hook rectangle is the same as the width of the
+ // caret rectangle.
+ if (isVertical) {
+ if (wm.IsSidewaysLR()) {
+ aHookRect->SetRect(aCaretRect->x + bidiIndicatorSize,
+ aCaretRect->y + (!isCaretRTL ? bidiIndicatorSize * -1
+ : aCaretRect->height),
+ aCaretRect->height, bidiIndicatorSize);
+ } else {
+ aHookRect->SetRect(aCaretRect->XMost() - bidiIndicatorSize,
+ aCaretRect->y + (isCaretRTL ? bidiIndicatorSize * -1
+ : aCaretRect->height),
+ aCaretRect->height, bidiIndicatorSize);
+ }
+ } else {
+ aHookRect->SetRect(aCaretRect->x + (isCaretRTL ? bidiIndicatorSize * -1
+ : aCaretRect->width),
+ aCaretRect->y + bidiIndicatorSize, bidiIndicatorSize,
+ aCaretRect->width);
+ }
+ }
+}
+
+/* static */
+void nsCaret::CaretBlinkCallback(nsITimer* aTimer, void* aClosure) {
+ nsCaret* theCaret = reinterpret_cast<nsCaret*>(aClosure);
+ if (!theCaret) {
+ return;
+ }
+ theCaret->mIsBlinkOn = !theCaret->mIsBlinkOn;
+ theCaret->SchedulePaint();
+
+ // mBlinkCount of -1 means blink count is not enabled.
+ if (theCaret->mBlinkCount == -1) {
+ return;
+ }
+
+ // Track the blink count, but only at end of a blink cycle.
+ if (theCaret->mIsBlinkOn) {
+ // If we exceeded the blink count, stop the timer.
+ if (--theCaret->mBlinkCount <= 0) {
+ theCaret->StopBlinking();
+ }
+ }
+}
+
+void nsCaret::SetIgnoreUserModify(bool aIgnoreUserModify) {
+ mIgnoreUserModify = aIgnoreUserModify;
+ SchedulePaint();
+}
diff --git a/layout/base/nsCaret.h b/layout/base/nsCaret.h
new file mode 100644
index 0000000000..43329c827f
--- /dev/null
+++ b/layout/base/nsCaret.h
@@ -0,0 +1,284 @@
+/* -*- 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/. */
+
+/* the caret is the text cursor used, e.g., when editing */
+
+#ifndef nsCaret_h__
+#define nsCaret_h__
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Selection.h"
+#include "nsCoord.h"
+#include "nsISelectionListener.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsPoint.h"
+#include "nsRect.h"
+
+class nsFrameSelection;
+class nsIContent;
+class nsIFrame;
+class nsINode;
+class nsITimer;
+
+namespace mozilla {
+class PresShell;
+enum class CaretAssociationHint;
+namespace gfx {
+class DrawTarget;
+} // namespace gfx
+} // namespace mozilla
+
+//-----------------------------------------------------------------------------
+class nsCaret final : public nsISelectionListener {
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+
+ public:
+ nsCaret();
+
+ protected:
+ virtual ~nsCaret();
+
+ public:
+ NS_DECL_ISUPPORTS
+
+ using CaretAssociationHint = mozilla::CaretAssociationHint;
+
+ nsresult Init(mozilla::PresShell* aPresShell);
+ void Terminate();
+
+ void SetSelection(mozilla::dom::Selection* aDOMSel);
+ mozilla::dom::Selection* GetSelection();
+
+ /**
+ * Sets whether the caret should only be visible in nodes that are not
+ * user-modify: read-only, or whether it should be visible in all nodes.
+ *
+ * @param aIgnoreUserModify true to have the cursor visible in all nodes,
+ * false to have it visible in all nodes except
+ * those with user-modify: read-only
+ */
+ void SetIgnoreUserModify(bool aIgnoreUserModify);
+ /** SetVisible will set the visibility of the caret
+ * @param inMakeVisible true to show the caret, false to hide it
+ */
+ void SetVisible(bool intMakeVisible);
+ /** IsVisible will get the visibility of the caret.
+ * This returns false if the caret is hidden because it was set
+ * to not be visible, or because the selection is not collapsed, or
+ * because an open popup is hiding the caret.
+ * It does not take account of blinking or the caret being hidden
+ * because we're in non-editable/disabled content.
+ */
+ bool IsVisible(mozilla::dom::Selection* aSelection = nullptr) {
+ if (!mVisible || mHideCount) {
+ return false;
+ }
+
+ if (!mShowDuringSelection) {
+ mozilla::dom::Selection* selection;
+ if (aSelection) {
+ selection = aSelection;
+ } else {
+ selection = GetSelection();
+ }
+ if (!selection || !selection->IsCollapsed()) {
+ return false;
+ }
+ }
+
+ if (IsMenuPopupHidingCaret()) {
+ return false;
+ }
+
+ return true;
+ }
+ /**
+ * AddForceHide() increases mHideCount and hide the caret even if
+ * SetVisible(true) has been or will be called. This is useful when the
+ * caller wants to hide caret temporarily and it needs to cancel later.
+ * Especially, in the latter case, it's too difficult to decide if the
+ * caret should be actually visible or not because caret visible state
+ * is set from a lot of event handlers. So, it's very stateful.
+ */
+ void AddForceHide();
+ /**
+ * RemoveForceHide() decreases mHideCount if it's over 0.
+ * If the value becomes 0, this may show the caret if SetVisible(true)
+ * has been called.
+ */
+ void RemoveForceHide();
+ /** SetCaretReadOnly set the appearance of the caret
+ * @param inMakeReadonly true to show the caret in a 'read only' state,
+ * false to show the caret in normal, editing state
+ */
+ void SetCaretReadOnly(bool inMakeReadonly);
+ /**
+ * @param aVisibility true if the caret should be visible even when the
+ * selection is not collapsed.
+ */
+ void SetVisibilityDuringSelection(bool aVisibility);
+
+ /**
+ * Set the caret's position explicitly to the specified node and offset
+ * instead of tracking its selection.
+ * Passing null for aNode would set the caret to track its selection again.
+ **/
+ void SetCaretPosition(nsINode* aNode, int32_t aOffset);
+
+ /**
+ * Schedule a repaint for the frame where the caret would appear.
+ * Does not check visibility etc.
+ */
+ void SchedulePaint(mozilla::dom::Selection* aSelection = nullptr);
+
+ /**
+ * Returns a frame to paint in, and the bounds of the painted caret
+ * relative to that frame.
+ * The rectangle includes bidi decorations.
+ * Returns null if the caret should not be drawn (including if it's blinked
+ * off).
+ */
+ nsIFrame* GetPaintGeometry(nsRect* aRect);
+ /**
+ * Same as the overload above, but returns the caret and hook rects
+ * separately, and also computes the color if requested.
+ */
+ nsIFrame* GetPaintGeometry(nsRect* aCaretRect, nsRect* aHookRect,
+ nscolor* aCaretColor = nullptr);
+ /**
+ * A simple wrapper around GetGeometry. Does not take any caret state into
+ * account other than the current selection.
+ */
+ nsIFrame* GetGeometry(nsRect* aRect) {
+ return GetGeometry(GetSelection(), aRect);
+ }
+
+ /** PaintCaret
+ * Actually paint the caret onto the given rendering context.
+ */
+ void PaintCaret(DrawTarget& aDrawTarget, nsIFrame* aForFrame,
+ const nsPoint& aOffset);
+
+ // nsISelectionListener interface
+ NS_DECL_NSISELECTIONLISTENER
+
+ /**
+ * Gets the position and size of the caret that would be drawn for
+ * the focus node/offset of aSelection (assuming it would be drawn,
+ * i.e., disregarding blink status). The geometry is stored in aRect,
+ * and we return the frame aRect is relative to.
+ * Only looks at the focus node of aSelection, so you can call it even if
+ * aSelection is not collapsed.
+ * This rect does not include any extra decorations for bidi.
+ * @param aRect must be non-null
+ */
+ static nsIFrame* GetGeometry(const mozilla::dom::Selection* aSelection,
+ nsRect* aRect);
+
+ static nsRect GetGeometryForFrame(nsIFrame* aFrame, int32_t aFrameOffset,
+ nscoord* aBidiIndicatorSize);
+
+ // Get the frame and frame offset based on the focus node and focus offset
+ // of aSelection. If aOverrideNode and aOverride are provided, use them
+ // instead.
+ // @param aFrameOffset return the frame offset if non-null.
+ // @param aUnadjustedFrame return the original frame that the selection is
+ // targeting, without any adjustment for painting.
+ // @return the frame of the focus node.
+ static nsIFrame* GetFrameAndOffset(const mozilla::dom::Selection* aSelection,
+ nsINode* aOverrideNode,
+ int32_t aOverrideOffset,
+ int32_t* aFrameOffset,
+ nsIFrame** aUnadjustedFrame = nullptr);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ protected:
+ static void CaretBlinkCallback(nsITimer* aTimer, void* aClosure);
+
+ void CheckSelectionLanguageChange();
+
+ void ResetBlinking();
+ void StopBlinking();
+
+ struct Metrics {
+ nscoord mBidiIndicatorSize; // width and height of bidi indicator
+ nscoord mCaretWidth; // full caret width including bidi indicator
+ };
+ static Metrics ComputeMetrics(nsIFrame* aFrame, int32_t aOffset,
+ nscoord aCaretHeight);
+ void ComputeCaretRects(nsIFrame* aFrame, int32_t aFrameOffset,
+ nsRect* aCaretRect, nsRect* aHookRect);
+
+ // Returns true if we should not draw the caret because of XUL menu popups.
+ // The caret should be hidden if:
+ // 1. An open popup contains the caret, but a menu popup exists before the
+ // caret-owning popup in the popup list (i.e. a menu is in front of the
+ // popup with the caret). If the menu itself contains the caret we don't
+ // hide it.
+ // 2. A menu popup is open, but there is no caret present in any popup.
+ // 3. The caret selection is empty.
+ bool IsMenuPopupHidingCaret();
+
+ nsWeakPtr mPresShell;
+ mozilla::WeakPtr<mozilla::dom::Selection> mDomSelectionWeak;
+
+ nsCOMPtr<nsITimer> mBlinkTimer;
+
+ /**
+ * The content to draw the caret at. If null, we use mDomSelectionWeak's
+ * focus node instead.
+ */
+ nsCOMPtr<nsINode> mOverrideContent;
+ /**
+ * The character offset to draw the caret at.
+ * Ignored if mOverrideContent is null.
+ */
+ int32_t mOverrideOffset;
+ /**
+ * mBlinkCount is used to control the number of times to blink the caret
+ * before stopping the blink. This is reset each time we reset the
+ * blinking.
+ */
+ int32_t mBlinkCount;
+ /**
+ * mBlinkRate is the rate of the caret blinking the last time we read it.
+ * It is used as a way to optimize whether we need to reset the blinking
+ * timer. 0 or a negative value means no blinking.
+ */
+ int32_t mBlinkRate;
+ /**
+ * mHideCount is not 0, it means that somebody doesn't want the caret
+ * to be visible. See AddForceHide() and RemoveForceHide().
+ */
+ uint32_t mHideCount;
+
+ /**
+ * mIsBlinkOn is true when we're in a blink cycle where the caret is on.
+ */
+ bool mIsBlinkOn;
+ /**
+ * mIsVisible is true when SetVisible was last called with 'true'.
+ */
+ bool mVisible;
+ /**
+ * mReadOnly is true when the caret is set to "read only" mode (i.e.,
+ * it doesn't blink).
+ */
+ bool mReadOnly;
+ /**
+ * mShowDuringSelection is true when the caret should be shown even when
+ * the selection is not collapsed.
+ */
+ bool mShowDuringSelection;
+ /**
+ * mIgnoreUserModify is true when the caret should be shown even when
+ * it's in non-user-modifiable content.
+ */
+ bool mIgnoreUserModify;
+};
+
+#endif // nsCaret_h__
diff --git a/layout/base/nsChangeHint.h b/layout/base/nsChangeHint.h
new file mode 100644
index 0000000000..5806abfd84
--- /dev/null
+++ b/layout/base/nsChangeHint.h
@@ -0,0 +1,513 @@
+/* -*- 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 what needs to be recomputed in response to style changes */
+
+#ifndef nsChangeHint_h___
+#define nsChangeHint_h___
+
+#include "mozilla/Types.h"
+#include "mozilla/Assertions.h"
+
+// Defines for various style related constants
+
+enum nsChangeHint : uint32_t {
+ nsChangeHint_Empty = 0,
+
+ // change was visual only (e.g., COLOR=)
+ // Invalidates all descendant frames (including following
+ // placeholders to out-of-flow frames).
+ nsChangeHint_RepaintFrame = 1 << 0,
+
+ // For reflow, we want flags to give us arbitrary FrameNeedsReflow behavior.
+ // just do a FrameNeedsReflow.
+ nsChangeHint_NeedReflow = 1 << 1,
+
+ // Invalidate intrinsic widths on the frame's ancestors. Must not be set
+ // without setting nsChangeHint_NeedReflow.
+ nsChangeHint_ClearAncestorIntrinsics = 1 << 2,
+
+ // Invalidate intrinsic widths on the frame's descendants. Must not be set
+ // without also setting nsChangeHint_ClearAncestorIntrinsics,
+ // nsChangeHint_NeedDirtyReflow and nsChangeHint_NeedReflow.
+ nsChangeHint_ClearDescendantIntrinsics = 1 << 3,
+
+ // Force unconditional reflow of all descendants. Must not be set without
+ // setting nsChangeHint_NeedReflow, but can be set regardless of whether the
+ // Clear*Intrinsics flags are set.
+ nsChangeHint_NeedDirtyReflow = 1 << 4,
+
+ // The currently shown mouse cursor needs to be updated
+ nsChangeHint_UpdateCursor = 1 << 5,
+
+ /**
+ * Used when the computed value (a URI) of one or more of an element's
+ * filter/mask/clip/etc CSS properties changes, causing the element's frame
+ * to start/stop referencing (or reference different) SVG resource elements.
+ * (_Not_ used to handle changes to referenced resource elements.) Using this
+ * hint results in SVGObserverUtils::UpdateEffects being called on the
+ * element's frame.
+ */
+ nsChangeHint_UpdateEffects = 1 << 6,
+
+ /**
+ * Visual change only, but the change can be handled entirely by
+ * updating the layer(s) for the frame.
+ * Updates all descendants (including following placeholders to out-of-flows).
+ */
+ nsChangeHint_UpdateOpacityLayer = 1 << 7,
+ /**
+ * Updates all descendants. Any placeholder descendants' out-of-flows
+ * are also descendants of the transformed frame, so they're updated.
+ */
+ nsChangeHint_UpdateTransformLayer = 1 << 8,
+
+ /**
+ * Change requires frame change (e.g., display:).
+ * Reconstructs all frame descendants, including following placeholders
+ * to out-of-flows.
+ *
+ * Note that this subsumes all the other change hints. (see
+ * RestyleManager::ProcessRestyledFrames for details).
+ */
+ nsChangeHint_ReconstructFrame = 1 << 9,
+
+ /**
+ * The frame's overflow area has changed. Does not update any descendant
+ * frames.
+ */
+ nsChangeHint_UpdateOverflow = 1 << 10,
+
+ /**
+ * The overflow area of the frame and all of its descendants has changed. This
+ * can happen through a text-decoration change.
+ */
+ nsChangeHint_UpdateSubtreeOverflow = 1 << 11,
+
+ /**
+ * The frame's overflow area has changed, through a change in its transform.
+ * In other words, the frame's pre-transform overflow is unchanged, but
+ * its post-transform overflow has changed, and thus its effect on its
+ * parent's overflow has changed. If the pre-transform overflow has
+ * changed, see nsChangeHint_UpdateOverflow.
+ * Does not update any descendant frames.
+ */
+ nsChangeHint_UpdatePostTransformOverflow = 1 << 12,
+
+ /**
+ * This frame's effect on its parent's overflow area has changed.
+ * (But neither its pre-transform nor post-transform overflow have
+ * changed; if those are the case, see
+ * nsChangeHint_UpdatePostTransformOverflow.)
+ */
+ nsChangeHint_UpdateParentOverflow = 1 << 13,
+
+ /**
+ * The children-only transform of an SVG frame changed, requiring overflows to
+ * be updated.
+ */
+ nsChangeHint_ChildrenOnlyTransform = 1 << 14,
+
+ /**
+ * The frame's offsets have changed, while its dimensions might have
+ * changed as well. This hint is used for positioned frames if their
+ * offset changes. If we decide that the dimensions are likely to
+ * change, this will trigger a reflow.
+ *
+ * Note that this should probably be used in combination with
+ * nsChangeHint_UpdateOverflow in order to get the overflow areas of
+ * the ancestors updated as well.
+ */
+ nsChangeHint_RecomputePosition = 1 << 15,
+
+ /**
+ * Behaves like ReconstructFrame, but only if the frame has descendants
+ * that are absolutely or fixed position. Use this hint when a style change
+ * has changed whether the frame is a container for fixed-pos or abs-pos
+ * elements, but reframing is otherwise not needed.
+ *
+ * Note that ComputedStyle::CalcStyleDifference adjusts results
+ * returned by style struct CalcDifference methods to return this hint
+ * only if there was a change to whether the element's overall style
+ * indicates that it establishes a containing block.
+ */
+ nsChangeHint_UpdateContainingBlock = 1 << 16,
+
+ /**
+ * This change hint has *no* change handling behavior. However, it
+ * exists to be a non-inherited hint, because when the border-style
+ * changes, and it's inherited by a child, that might require a reflow
+ * due to the border-width change on the child.
+ */
+ nsChangeHint_BorderStyleNoneChange = 1 << 17,
+
+ /**
+ * This will schedule an invalidating paint. This is useful if something
+ * has changed which will be invalidated by DLBI.
+ */
+ nsChangeHint_SchedulePaint = 1 << 18,
+
+ /**
+ * A hint reflecting that style data changed with no change handling
+ * behavior. We need to return this, rather than nsChangeHint(0),
+ * so that certain optimizations that manipulate the style tree are
+ * correct.
+ *
+ * nsChangeHint_NeutralChange must be returned by CalcDifference on a given
+ * style struct if the data in the style structs are meaningfully different
+ * and if no other change hints are returned. If any other change hints are
+ * set, then nsChangeHint_NeutralChange need not also be included, but it is
+ * safe to do so. (An example of style structs having non-meaningfully
+ * different data would be cached information that would be re-calculated
+ * to the same values, such as nsStyleBorder::mSubImages.)
+ */
+ nsChangeHint_NeutralChange = 1 << 19,
+
+ /**
+ * This will cause rendering observers to be invalidated.
+ */
+ nsChangeHint_InvalidateRenderingObservers = 1 << 20,
+
+ /**
+ * Indicates that the reflow changes the size or position of the
+ * element, and thus the reflow must start from at least the frame's
+ * parent. Must be not be set without also setting nsChangeHint_NeedReflow.
+ * And consider adding nsChangeHint_ClearAncestorIntrinsics if needed.
+ */
+ nsChangeHint_ReflowChangesSizeOrPosition = 1 << 21,
+
+ /**
+ * Indicates that the style changes the computed BSize --- e.g. 'height'.
+ * Must not be set without also setting nsChangeHint_NeedReflow.
+ */
+ nsChangeHint_UpdateComputedBSize = 1 << 22,
+
+ /**
+ * Indicates that the 'opacity' property changed between 1 and non-1.
+ *
+ * Used as extra data for handling UpdateOpacityLayer hints.
+ *
+ * Note that we do not send this hint if the non-1 value was 0.99 or
+ * greater, since in that case we send a RepaintFrame hint instead.
+ */
+ nsChangeHint_UpdateUsesOpacity = 1 << 23,
+
+ /**
+ * Indicates that the 'background-position' property changed.
+ * Regular frames can invalidate these changes using DLBI, but
+ * for some frame types we need to repaint the whole frame because
+ * the frame does not build individual background image display items
+ * for each background layer.
+ */
+ nsChangeHint_UpdateBackgroundPosition = 1 << 24,
+
+ /**
+ * Indicates that a frame has changed to or from having the CSS
+ * transform property set.
+ */
+ nsChangeHint_AddOrRemoveTransform = 1 << 25,
+
+ /**
+ * Indicates that the presence of scrollbars might have changed.
+ *
+ * This happens when at least one of overflow-{x,y} properties changed.
+ *
+ * In most cases, this is equivalent to nsChangeHint_ReconstructFrame. But
+ * in some special cases where the change is really targeting the viewport's
+ * scrollframe, this is instead equivalent to nsChangeHint_AllReflowHints
+ * (because the viewport always has an associated scrollframe).
+ */
+ nsChangeHint_ScrollbarChange = 1 << 26,
+
+ /**
+ * Indicates that there has been a colspan or rowspan attribute change
+ * on the cells of a table.
+ */
+ nsChangeHint_UpdateTableCellSpans = 1 << 27,
+
+ /**
+ * Indicates that the visiblity property changed.
+ * This change hint is used for skip restyling for animations on
+ * visibility:hidden elements in the case where the elements have no visible
+ * descendants.
+ */
+ nsChangeHint_VisibilityChange = 1u << 28,
+
+ // IMPORTANT NOTE: When adding a new hint, you will need to add it to
+ // one of:
+ //
+ // * nsChangeHint_Hints_NeverHandledForDescendants
+ // * nsChangeHint_Hints_AlwaysHandledForDescendants
+ // * nsChangeHint_Hints_SometimesHandledForDescendants
+ //
+ // and you also may need to handle it in NS_HintsNotHandledForDescendantsIn.
+ //
+ // Please also add it to RestyleManager::ChangeHintToString and
+ // modify nsChangeHint_AllHints below accordingly.
+
+ /**
+ * Dummy hint value for all hints. It exists for compile time check.
+ */
+ nsChangeHint_AllHints = uint32_t((1ull << 29) - 1),
+};
+
+// Redefine these operators to return nothing. This will catch any use
+// of these operators on hints. We should not be using these operators
+// on nsChangeHints
+inline void operator<(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator>(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator!=(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator==(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator<=(nsChangeHint s1, nsChangeHint s2) {}
+inline void operator>=(nsChangeHint s1, nsChangeHint s2) {}
+
+// Operators on nsChangeHints
+
+// Returns true iff the second hint contains all the hints of the first hint
+inline bool NS_IsHintSubset(nsChangeHint aSubset, nsChangeHint aSuperSet) {
+ return (aSubset & aSuperSet) == aSubset;
+}
+
+// The functions below need an integral type to cast to to avoid
+// infinite recursion.
+typedef decltype(nsChangeHint(0) + nsChangeHint(0)) nsChangeHint_size_t;
+
+inline nsChangeHint constexpr operator|(nsChangeHint aLeft,
+ nsChangeHint aRight) {
+ return nsChangeHint(nsChangeHint_size_t(aLeft) | nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint constexpr operator&(nsChangeHint aLeft,
+ nsChangeHint aRight) {
+ return nsChangeHint(nsChangeHint_size_t(aLeft) & nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint& operator|=(nsChangeHint& aLeft, nsChangeHint aRight) {
+ return aLeft = aLeft | aRight;
+}
+
+inline nsChangeHint& operator&=(nsChangeHint& aLeft, nsChangeHint aRight) {
+ return aLeft = aLeft & aRight;
+}
+
+inline nsChangeHint constexpr operator~(nsChangeHint aArg) {
+ return nsChangeHint(~nsChangeHint_size_t(aArg));
+}
+
+inline nsChangeHint constexpr operator^(nsChangeHint aLeft,
+ nsChangeHint aRight) {
+ return nsChangeHint(nsChangeHint_size_t(aLeft) ^ nsChangeHint_size_t(aRight));
+}
+
+inline nsChangeHint operator^=(nsChangeHint& aLeft, nsChangeHint aRight) {
+ return aLeft = aLeft ^ aRight;
+}
+
+/**
+ * We have an optimization when processing change hints which prevents
+ * us from visiting the descendants of a node when a hint on that node
+ * is being processed. This optimization does not apply in some of the
+ * cases where applying a hint to an element does not necessarily result
+ * in the same hint being handled on the descendants.
+ */
+
+// The change hints that are always handled for descendants.
+#define nsChangeHint_Hints_AlwaysHandledForDescendants \
+ (nsChangeHint_ClearDescendantIntrinsics | nsChangeHint_NeedDirtyReflow | \
+ nsChangeHint_NeutralChange | nsChangeHint_ReconstructFrame | \
+ nsChangeHint_RepaintFrame | nsChangeHint_SchedulePaint | \
+ nsChangeHint_UpdateCursor | nsChangeHint_UpdateSubtreeOverflow | \
+ nsChangeHint_VisibilityChange)
+
+// The change hints that are never handled for descendants.
+#define nsChangeHint_Hints_NeverHandledForDescendants \
+ (nsChangeHint_BorderStyleNoneChange | nsChangeHint_ChildrenOnlyTransform | \
+ nsChangeHint_ScrollbarChange | nsChangeHint_InvalidateRenderingObservers | \
+ nsChangeHint_RecomputePosition | nsChangeHint_UpdateBackgroundPosition | \
+ nsChangeHint_UpdateComputedBSize | nsChangeHint_UpdateContainingBlock | \
+ nsChangeHint_UpdateEffects | nsChangeHint_UpdateOpacityLayer | \
+ nsChangeHint_UpdateOverflow | nsChangeHint_UpdateParentOverflow | \
+ nsChangeHint_UpdatePostTransformOverflow | \
+ nsChangeHint_UpdateTableCellSpans | nsChangeHint_UpdateTransformLayer | \
+ nsChangeHint_UpdateUsesOpacity | nsChangeHint_AddOrRemoveTransform)
+
+// The change hints that are sometimes considered to be handled for descendants.
+#define nsChangeHint_Hints_SometimesHandledForDescendants \
+ (nsChangeHint_ClearAncestorIntrinsics | nsChangeHint_NeedReflow | \
+ nsChangeHint_ReflowChangesSizeOrPosition)
+
+static_assert(!(nsChangeHint_Hints_AlwaysHandledForDescendants &
+ nsChangeHint_Hints_NeverHandledForDescendants) &&
+ !(nsChangeHint_Hints_AlwaysHandledForDescendants &
+ nsChangeHint_Hints_SometimesHandledForDescendants) &&
+ !(nsChangeHint_Hints_NeverHandledForDescendants &
+ nsChangeHint_Hints_SometimesHandledForDescendants) &&
+ !(nsChangeHint_AllHints ^
+ nsChangeHint_Hints_AlwaysHandledForDescendants ^
+ nsChangeHint_Hints_NeverHandledForDescendants ^
+ nsChangeHint_Hints_SometimesHandledForDescendants),
+ "change hints must be present in exactly one of "
+ "nsChangeHint_Hints_{Always,Never,Sometimes}"
+ "HandledForDescendants");
+
+// The most hints that NS_HintsNotHandledForDescendantsIn could possibly return:
+#define nsChangeHint_Hints_NotHandledForDescendants \
+ (nsChangeHint_Hints_NeverHandledForDescendants | \
+ nsChangeHint_Hints_SometimesHandledForDescendants)
+
+// Redefine the old NS_STYLE_HINT constants in terms of the new hint structure
+#define NS_STYLE_HINT_VISUAL \
+ nsChangeHint(nsChangeHint_RepaintFrame | nsChangeHint_SchedulePaint)
+#define nsChangeHint_AllReflowHints \
+ nsChangeHint( \
+ nsChangeHint_NeedReflow | nsChangeHint_ReflowChangesSizeOrPosition | \
+ nsChangeHint_ClearAncestorIntrinsics | \
+ nsChangeHint_ClearDescendantIntrinsics | nsChangeHint_NeedDirtyReflow)
+
+// Below are the change hints that we send for ISize & BSize changes.
+// Each is similar to nsChangeHint_AllReflowHints with a few changes.
+
+// * For an ISize change, we send nsChangeHint_AllReflowHints, with two bits
+// excluded: nsChangeHint_ClearDescendantIntrinsics (because an ancestor's
+// inline-size change can't affect descendant intrinsic sizes), and
+// nsChangeHint_NeedDirtyReflow (because ISize changes don't need to *force*
+// all descendants to reflow).
+#define nsChangeHint_ReflowHintsForISizeChange \
+ nsChangeHint(nsChangeHint_AllReflowHints & \
+ ~(nsChangeHint_ClearDescendantIntrinsics | \
+ nsChangeHint_NeedDirtyReflow))
+
+// * For a BSize change, we send almost the same hints as for ISize changes,
+// with one extra: nsChangeHint_UpdateComputedBSize. We need this hint because
+// BSize changes CAN affect descendant intrinsic sizes, due to replaced
+// elements with percentage BSizes in descendants which also have percentage
+// BSizes. nsChangeHint_UpdateComputedBSize clears intrinsic sizes for frames
+// that have such replaced elements. (We could instead send
+// nsChangeHint_ClearDescendantIntrinsics, but that's broader than we need.)
+//
+// NOTE: You might think that BSize changes could exclude
+// nsChangeHint_ClearAncestorIntrinsics (which is inline-axis specific), but we
+// do need to send it, to clear cached results from CSS Flex measuring reflows.
+#define nsChangeHint_ReflowHintsForBSizeChange \
+ nsChangeHint( \
+ (nsChangeHint_AllReflowHints | nsChangeHint_UpdateComputedBSize) & \
+ ~(nsChangeHint_ClearDescendantIntrinsics | \
+ nsChangeHint_NeedDirtyReflow))
+
+// For a change in whether a scrollframe displays or not scrollbars.
+//
+// When requesting this reflow, we send the exact same change hints that "width"
+// and "height" would send (since conceptually, adding/removing scrollbars is
+// like changing the available space).
+//
+// FIXME(emilio): Seems we could be a bit more efficient here, as adding or
+// removing scrollbars doesn't change the size of the element itself, so maybe
+// ClearAncestorIntrinsics or ReflowChangesSizeOrPosition are not needed... I
+// think this ideally should be just nsChangehint_NeedReflow.
+#define nsChangeHint_ReflowHintsForScrollbarChange \
+ nsChangeHint(nsChangeHint_ReflowHintsForBSizeChange | \
+ nsChangeHint_ReflowHintsForISizeChange)
+
+// * For changes to the float area of an already-floated element, we need all
+// reflow hints, but not the ones that apply to descendants.
+// Our descendants aren't impacted when our float area only changes
+// placement but not size/shape. (e.g. if we change which side we float to).
+// But our ancestors/siblings are potentially impacted, so we need to send
+// the non-descendant reflow hints.
+#define nsChangeHint_ReflowHintsForFloatAreaChange \
+ nsChangeHint(nsChangeHint_AllReflowHints & \
+ ~(nsChangeHint_ClearDescendantIntrinsics | \
+ nsChangeHint_NeedDirtyReflow))
+
+#define NS_STYLE_HINT_REFLOW \
+ nsChangeHint(NS_STYLE_HINT_VISUAL | nsChangeHint_AllReflowHints)
+
+// Change hints for added or removed transform style.
+//
+// If we've added or removed the transform property, we need to reconstruct the
+// frame to add or remove the view object, and also to handle abs-pos and
+// fixed-pos containers.
+//
+// We do not need to apply nsChangeHint_UpdateTransformLayer since
+// nsChangeHint_RepaintFrame will forcibly invalidate the frame area and
+// ensure layers are rebuilt (or removed).
+#define nsChangeHint_ComprehensiveAddOrRemoveTransform \
+ nsChangeHint(nsChangeHint_UpdateContainingBlock | \
+ nsChangeHint_AddOrRemoveTransform | \
+ nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame)
+
+// NB: Once we drop support for the old style system, this logic should be
+// inlined in the Servo style system to eliminate the FFI call.
+inline nsChangeHint NS_HintsNotHandledForDescendantsIn(
+ nsChangeHint aChangeHint) {
+ nsChangeHint result =
+ aChangeHint & nsChangeHint_Hints_NeverHandledForDescendants;
+
+ if (!(aChangeHint & nsChangeHint_NeedDirtyReflow)) {
+ if (aChangeHint & nsChangeHint_NeedReflow) {
+ // If NeedDirtyReflow is *not* set, then NeedReflow is a
+ // non-inherited hint.
+ result |= nsChangeHint_NeedReflow;
+ }
+
+ if (aChangeHint & nsChangeHint_ReflowChangesSizeOrPosition) {
+ // If NeedDirtyReflow is *not* set, then ReflowChangesSizeOrPosition is a
+ // non-inherited hint.
+ result |= nsChangeHint_ReflowChangesSizeOrPosition;
+ }
+ }
+
+ if (!(aChangeHint & nsChangeHint_ClearDescendantIntrinsics) &&
+ (aChangeHint & nsChangeHint_ClearAncestorIntrinsics)) {
+ // If ClearDescendantIntrinsics is *not* set, then
+ // ClearAncestorIntrinsics is a non-inherited hint.
+ result |= nsChangeHint_ClearAncestorIntrinsics;
+ }
+
+ MOZ_ASSERT(
+ NS_IsHintSubset(result, nsChangeHint_Hints_NotHandledForDescendants),
+ "something is inconsistent");
+
+ return result;
+}
+
+inline nsChangeHint NS_HintsHandledForDescendantsIn(nsChangeHint aChangeHint) {
+ return aChangeHint & ~NS_HintsNotHandledForDescendantsIn(aChangeHint);
+}
+
+// Returns the change hints in aOurChange that are not subsumed by those
+// in aHintsHandled (which are hints that have been handled by an ancestor).
+inline nsChangeHint NS_RemoveSubsumedHints(nsChangeHint aOurChange,
+ nsChangeHint aHintsHandled) {
+ nsChangeHint result =
+ aOurChange & ~NS_HintsHandledForDescendantsIn(aHintsHandled);
+
+ if (result &
+ (nsChangeHint_ClearAncestorIntrinsics |
+ nsChangeHint_ClearDescendantIntrinsics | nsChangeHint_NeedDirtyReflow |
+ nsChangeHint_ReflowChangesSizeOrPosition |
+ nsChangeHint_UpdateComputedBSize)) {
+ result |= nsChangeHint_NeedReflow;
+ }
+
+ if (result & (nsChangeHint_ClearDescendantIntrinsics)) {
+ MOZ_ASSERT(result & nsChangeHint_ClearAncestorIntrinsics);
+ result |= // nsChangeHint_ClearAncestorIntrinsics |
+ nsChangeHint_NeedDirtyReflow;
+ }
+
+ return result;
+}
+
+namespace mozilla {
+
+struct StyleRestyleHint;
+
+using RestyleHint = StyleRestyleHint;
+
+} // namespace mozilla
+
+#endif /* nsChangeHint_h___ */
diff --git a/layout/base/nsCompatibility.h b/layout/base/nsCompatibility.h
new file mode 100644
index 0000000000..eeb061ac11
--- /dev/null
+++ b/layout/base/nsCompatibility.h
@@ -0,0 +1,18 @@
+/* -*- 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 quirks mode, standards mode, and almost standards mode */
+
+#ifndef nsCompatibility_h___
+#define nsCompatibility_h___
+
+enum nsCompatibility {
+ eCompatibility_FullStandards = 1,
+ eCompatibility_AlmostStandards = 2,
+ eCompatibility_NavQuirks = 3
+};
+
+#endif /* nsCompatibility_h___ */
diff --git a/layout/base/nsCounterManager.cpp b/layout/base/nsCounterManager.cpp
new file mode 100644
index 0000000000..7c2e9bb776
--- /dev/null
+++ b/layout/base/nsCounterManager.cpp
@@ -0,0 +1,552 @@
+/* -*- 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 CSS counters (for numbering things) */
+
+#include "nsCounterManager.h"
+
+#include "mozilla/AutoRestore.h"
+#include "mozilla/ContainStyleScopeManager.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/Likely.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Text.h"
+#include "nsContainerFrame.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIFrame.h"
+#include "nsTArray.h"
+
+using namespace mozilla;
+
+bool nsCounterUseNode::InitTextFrame(nsGenConList* aList,
+ nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame) {
+ nsCounterNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
+
+ auto* counterList = static_cast<nsCounterList*>(aList);
+ counterList->Insert(this);
+ aPseudoFrame->AddStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE);
+ // If the list is already dirty, or the node is not at the end, just start
+ // with an empty string for now and when we recalculate the list we'll change
+ // the value to the right one.
+ if (counterList->IsDirty()) {
+ return false;
+ }
+ if (!counterList->IsLast(this)) {
+ counterList->SetDirty();
+ return true;
+ }
+ Calc(counterList, /* aNotify = */ false);
+ return false;
+}
+
+// assign the correct |mValueAfter| value to a node that has been inserted
+// Should be called immediately after calling |Insert|.
+void nsCounterUseNode::Calc(nsCounterList* aList, bool aNotify) {
+ NS_ASSERTION(aList->IsRecalculatingAll() || !aList->IsDirty(),
+ "Why are we calculating with a dirty list?");
+
+ mValueAfter = nsCounterList::ValueBefore(this);
+
+ if (mText) {
+ nsAutoString contentString;
+ GetText(contentString);
+ mText->SetText(contentString, aNotify);
+ }
+}
+
+// assign the correct |mValueAfter| value to a node that has been inserted
+// Should be called immediately after calling |Insert|.
+void nsCounterChangeNode::Calc(nsCounterList* aList) {
+ NS_ASSERTION(aList->IsRecalculatingAll() || !aList->IsDirty(),
+ "Why are we calculating with a dirty list?");
+ if (IsContentBasedReset()) {
+ // RecalcAll takes care of this case.
+ } else if (mType == RESET || mType == SET) {
+ mValueAfter = mChangeValue;
+ } else {
+ NS_ASSERTION(mType == INCREMENT, "invalid type");
+ mValueAfter = nsCounterManager::IncrementCounter(
+ nsCounterList::ValueBefore(this), mChangeValue);
+ }
+}
+
+void nsCounterUseNode::GetText(nsString& aResult) {
+ CounterStyle* style =
+ mPseudoFrame->PresContext()->CounterStyleManager()->ResolveCounterStyle(
+ mCounterStyle);
+ GetText(mPseudoFrame->GetWritingMode(), style, aResult);
+}
+
+void nsCounterUseNode::GetText(WritingMode aWM, CounterStyle* aStyle,
+ nsString& aResult) {
+ const bool isBidiRTL = aWM.IsBidiRTL();
+ auto AppendCounterText = [&aResult, isBidiRTL](const nsAutoString& aText,
+ bool aIsRTL) {
+ if (MOZ_LIKELY(isBidiRTL == aIsRTL)) {
+ aResult.Append(aText);
+ } else {
+ // RLM = 0x200f, LRM = 0x200e
+ const char16_t mark = aIsRTL ? 0x200f : 0x200e;
+ aResult.Append(mark);
+ aResult.Append(aText);
+ aResult.Append(mark);
+ }
+ };
+
+ if (mForLegacyBullet) {
+ nsAutoString prefix;
+ aStyle->GetPrefix(prefix);
+ aResult.Assign(prefix);
+ }
+
+ AutoTArray<nsCounterNode*, 8> stack;
+ stack.AppendElement(static_cast<nsCounterNode*>(this));
+
+ if (mAllCounters && mScopeStart) {
+ for (nsCounterNode* n = mScopeStart; n->mScopePrev; n = n->mScopeStart) {
+ stack.AppendElement(n->mScopePrev);
+ }
+ }
+
+ for (nsCounterNode* n : Reversed(stack)) {
+ nsAutoString text;
+ bool isTextRTL;
+ aStyle->GetCounterText(n->mValueAfter, aWM, text, isTextRTL);
+ if (!mForLegacyBullet || aStyle->IsBullet()) {
+ aResult.Append(text);
+ } else {
+ AppendCounterText(text, isTextRTL);
+ }
+ if (n == this) {
+ break;
+ }
+ aResult.Append(mSeparator);
+ }
+
+ if (mForLegacyBullet) {
+ nsAutoString suffix;
+ aStyle->GetSuffix(suffix);
+ aResult.Append(suffix);
+ }
+}
+
+static const nsIContent* GetParentContentForScope(nsIFrame* frame) {
+ // We do not want elements with `display: contents` to establish scope for
+ // counters. We'd like to do something like
+ // `nsIFrame::GetClosestFlattenedTreeAncestorPrimaryFrame()` above, but this
+ // may be called before the primary frame is set on frames.
+ nsIContent* content = frame->GetContent()->GetFlattenedTreeParent();
+ while (content && content->IsElement() &&
+ content->AsElement()->IsDisplayContents()) {
+ content = content->GetFlattenedTreeParent();
+ }
+
+ return content;
+}
+
+bool nsCounterList::IsDirty() const {
+ return mScope->GetScopeManager().CounterDirty(mCounterName);
+}
+
+void nsCounterList::SetDirty() {
+ mScope->GetScopeManager().SetCounterDirty(mCounterName);
+}
+
+void nsCounterList::SetScope(nsCounterNode* aNode) {
+ // This function is responsible for setting |mScopeStart| and
+ // |mScopePrev| (whose purpose is described in nsCounterManager.h).
+ // We do this by starting from the node immediately preceding
+ // |aNode| in content tree order, which is reasonably likely to be
+ // the previous element in our scope (or, for a reset, the previous
+ // element in the containing scope, which is what we want). If
+ // we're not in the same scope that it is, then it's too deep in the
+ // frame tree, so we walk up parent scopes until we find something
+ // appropriate.
+
+ auto setNullScopeFor = [](nsCounterNode* aNode) {
+ aNode->mScopeStart = nullptr;
+ aNode->mScopePrev = nullptr;
+ aNode->mCrossesContainStyleBoundaries = false;
+ if (aNode->IsUnitializedIncrementNode()) {
+ aNode->ChangeNode()->mChangeValue = 1;
+ }
+ };
+
+ if (aNode == First() && aNode->mType != nsCounterNode::USE) {
+ setNullScopeFor(aNode);
+ return;
+ }
+
+ auto didSetScopeFor = [this](nsCounterNode* aNode) {
+ if (aNode->mType == nsCounterNode::USE) {
+ return;
+ }
+ if (aNode->mScopeStart->IsContentBasedReset()) {
+ SetDirty();
+ }
+ if (aNode->IsUnitializedIncrementNode()) {
+ aNode->ChangeNode()->mChangeValue =
+ aNode->mScopeStart->IsReversed() ? -1 : 1;
+ }
+ };
+
+ // If there exist an explicit RESET scope created by an ancestor or
+ // the element itself, then we use that scope.
+ // Otherwise, fall through to consider scopes created by siblings (and
+ // their descendants) in reverse document order.
+ if (aNode->mType != nsCounterNode::USE &&
+ StaticPrefs::layout_css_counter_ancestor_scope_enabled()) {
+ for (auto* p = aNode->mPseudoFrame; p; p = p->GetParent()) {
+ // This relies on the fact that a RESET node is always the first
+ // CounterNode for a frame if it has any.
+ auto* counter = GetFirstNodeFor(p);
+ if (!counter || counter->mType != nsCounterNode::RESET) {
+ continue;
+ }
+ if (p == aNode->mPseudoFrame) {
+ break;
+ }
+ aNode->mScopeStart = counter;
+ aNode->mScopePrev = counter;
+ aNode->mCrossesContainStyleBoundaries = false;
+ for (nsCounterNode* prev = Prev(aNode); prev; prev = prev->mScopePrev) {
+ if (prev->mScopeStart == counter) {
+ aNode->mScopePrev =
+ prev->mType == nsCounterNode::RESET ? prev->mScopePrev : prev;
+ break;
+ }
+ if (prev->mType != nsCounterNode::RESET) {
+ prev = prev->mScopeStart;
+ if (!prev) {
+ break;
+ }
+ }
+ }
+ didSetScopeFor(aNode);
+ return;
+ }
+ }
+
+ // Get the content node for aNode's rendering object's *parent*,
+ // since scope includes siblings, so we want a descendant check on
+ // parents. Note here that mPseudoFrame is a bit of a misnomer, as it
+ // might not be a pseudo element at all, but a normal element that
+ // happens to increment a counter. We want to respect the flat tree
+ // here, but skipping any <slot> element that happens to contain
+ // mPseudoFrame. That's why this uses GetInFlowParent() instead
+ // of GetFlattenedTreeParent().
+ const nsIContent* nodeContent = GetParentContentForScope(aNode->mPseudoFrame);
+ if (SetScopeByWalkingBackwardThroughList(aNode, nodeContent, Prev(aNode))) {
+ aNode->mCrossesContainStyleBoundaries = false;
+ didSetScopeFor(aNode);
+ return;
+ }
+
+ // If this is a USE node there's a possibility that its counter scope starts
+ // in a parent `contain: style` scope. Look upward in the `contain: style`
+ // scope tree to find an appropriate node with which this node shares a
+ // counter scope.
+ if (aNode->mType == nsCounterNode::USE && aNode == First()) {
+ for (auto* scope = mScope->GetParent(); scope; scope = scope->GetParent()) {
+ if (auto* counterList =
+ scope->GetCounterManager().GetCounterList(mCounterName)) {
+ if (auto* node = static_cast<nsCounterNode*>(
+ mScope->GetPrecedingElementInGenConList(counterList))) {
+ if (SetScopeByWalkingBackwardThroughList(aNode, nodeContent, node)) {
+ aNode->mCrossesContainStyleBoundaries = true;
+ didSetScopeFor(aNode);
+ return;
+ }
+ }
+ }
+ }
+ }
+
+ setNullScopeFor(aNode);
+}
+
+bool nsCounterList::SetScopeByWalkingBackwardThroughList(
+ nsCounterNode* aNodeToSetScopeFor, const nsIContent* aNodeContent,
+ nsCounterNode* aNodeToBeginLookingAt) {
+ for (nsCounterNode *prev = aNodeToBeginLookingAt, *start; prev;
+ prev = start->mScopePrev) {
+ // There are two possibilities here:
+ // 1. |prev| starts a new counter scope. This happens when:
+ // a. It's a reset node.
+ // b. It's an implied reset node which we know because mScopeStart is null.
+ // c. It follows one or more USE nodes at the start of the list which have
+ // a scope that starts in a parent `contain: style` context.
+ // In all of these cases, |prev| should be the start of this node's counter
+ // scope.
+ // 2. |prev| does not start a new counter scope and this node should share a
+ // counter scope start with |prev|.
+ start =
+ (prev->mType == nsCounterNode::RESET || !prev->mScopeStart ||
+ (prev->mScopePrev && prev->mScopePrev->mCrossesContainStyleBoundaries))
+ ? prev
+ : prev->mScopeStart;
+
+ const nsIContent* startContent =
+ GetParentContentForScope(start->mPseudoFrame);
+ NS_ASSERTION(aNodeContent || !startContent,
+ "null check on startContent should be sufficient to "
+ "null check aNodeContent as well, since if aNodeContent "
+ "is for the root, startContent (which is before it) "
+ "must be too");
+
+ // A reset's outer scope can't be a scope created by a sibling.
+ if (!(aNodeToSetScopeFor->mType == nsCounterNode::RESET &&
+ aNodeContent == startContent) &&
+ // everything is inside the root (except the case above,
+ // a second reset on the root)
+ (!startContent ||
+ aNodeContent->IsInclusiveFlatTreeDescendantOf(startContent))) {
+ // If this node is a USE node and the previous node was also a USE node
+ // which has a scope that starts in a parent `contain: style` context,
+ // this node's scope shares the same scope and crosses `contain: style`
+ // scope boundaries.
+ if (aNodeToSetScopeFor->mType == nsCounterNode::USE) {
+ aNodeToSetScopeFor->mCrossesContainStyleBoundaries =
+ prev->mCrossesContainStyleBoundaries;
+ }
+
+ aNodeToSetScopeFor->mScopeStart = start;
+ aNodeToSetScopeFor->mScopePrev = prev;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void nsCounterList::RecalcAll() {
+ AutoRestore<bool> restoreRecalculatingAll(mRecalculatingAll);
+ mRecalculatingAll = true;
+
+ // Setup the scope and calculate the default start value for content-based
+ // reversed() counters. We need to track the last increment for each of
+ // those scopes so that we can add it in an extra time at the end.
+ // https://drafts.csswg.org/css-lists/#instantiating-counters
+ nsTHashMap<nsPtrHashKey<nsCounterChangeNode>, int32_t> scopes;
+ for (nsCounterNode* node = First(); node; node = Next(node)) {
+ SetScope(node);
+ if (node->IsContentBasedReset()) {
+ node->ChangeNode()->mSeenSetNode = false;
+ node->mValueAfter = 0;
+ scopes.InsertOrUpdate(node->ChangeNode(), 0);
+ } else if (node->mScopeStart && node->mScopeStart->IsContentBasedReset() &&
+ !node->mScopeStart->ChangeNode()->mSeenSetNode) {
+ if (node->mType == nsCounterChangeNode::INCREMENT) {
+ auto incrementNegated = -node->ChangeNode()->mChangeValue;
+ if (auto entry = scopes.Lookup(node->mScopeStart->ChangeNode())) {
+ entry.Data() = incrementNegated;
+ }
+ auto* next = Next(node);
+ if (next && next->mPseudoFrame == node->mPseudoFrame &&
+ next->mType == nsCounterChangeNode::SET) {
+ continue;
+ }
+ node->mScopeStart->mValueAfter += incrementNegated;
+ } else if (node->mType == nsCounterChangeNode::SET) {
+ node->mScopeStart->mValueAfter += node->ChangeNode()->mChangeValue;
+ // We have a 'counter-set' for this scope so we're done.
+ // The counter is incremented from that value for the remaining nodes.
+ node->mScopeStart->ChangeNode()->mSeenSetNode = true;
+ }
+ }
+ }
+
+ // For all the content-based reversed() counters we found, add in the
+ // incrementNegated from its last counter-increment.
+ for (auto iter = scopes.ConstIter(); !iter.Done(); iter.Next()) {
+ iter.Key()->mValueAfter += iter.Data();
+ }
+
+ for (nsCounterNode* node = First(); node; node = Next(node)) {
+ node->Calc(this, /* aNotify = */ true);
+ }
+}
+
+static bool AddCounterChangeNode(nsCounterManager& aManager, nsIFrame* aFrame,
+ int32_t aIndex,
+ const nsStyleContent::CounterPair& aPair,
+ nsCounterNode::Type aType) {
+ auto* node = new nsCounterChangeNode(aFrame, aType, aPair.value, aIndex,
+ aPair.is_reversed);
+ nsCounterList* counterList =
+ aManager.GetOrCreateCounterList(aPair.name.AsAtom());
+ counterList->Insert(node);
+ if (!counterList->IsLast(node)) {
+ // Tell the caller it's responsible for recalculating the entire list.
+ counterList->SetDirty();
+ return true;
+ }
+
+ // Don't call Calc() if the list is already dirty -- it'll be recalculated
+ // anyway, and trying to calculate with a dirty list doesn't work.
+ if (MOZ_LIKELY(!counterList->IsDirty())) {
+ node->Calc(counterList);
+ }
+ return counterList->IsDirty();
+}
+
+static bool HasCounters(const nsStyleContent& aStyle) {
+ return !aStyle.mCounterIncrement.IsEmpty() ||
+ !aStyle.mCounterReset.IsEmpty() || !aStyle.mCounterSet.IsEmpty();
+}
+
+bool nsCounterManager::AddCounterChanges(nsIFrame* aFrame) {
+ // For elements with 'display:list-item' we add a default
+ // 'counter-increment:list-item' unless 'counter-increment' already has a
+ // value for 'list-item'.
+ //
+ // https://drafts.csswg.org/css-lists-3/#declaring-a-list-item
+ //
+ // We inherit `display` for some anonymous boxes, but we don't want them to
+ // increment the list-item counter.
+ const bool requiresListItemIncrement =
+ aFrame->StyleDisplay()->IsListItem() && !aFrame->Style()->IsAnonBox();
+
+ const nsStyleContent* styleContent = aFrame->StyleContent();
+
+ if (!requiresListItemIncrement && !HasCounters(*styleContent)) {
+ MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE));
+ return false;
+ }
+
+ aFrame->AddStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE);
+
+ bool dirty = false;
+ // Add in order, resets first, so all the comparisons will be optimized
+ // for addition at the end of the list.
+ {
+ int32_t i = 0;
+ for (const auto& pair : styleContent->mCounterReset.AsSpan()) {
+ dirty |= AddCounterChangeNode(*this, aFrame, i++, pair,
+ nsCounterChangeNode::RESET);
+ }
+ }
+ bool hasListItemIncrement = false;
+ {
+ int32_t i = 0;
+ for (const auto& pair : styleContent->mCounterIncrement.AsSpan()) {
+ hasListItemIncrement |= pair.name.AsAtom() == nsGkAtoms::list_item;
+ if (pair.value != 0) {
+ dirty |= AddCounterChangeNode(*this, aFrame, i++, pair,
+ nsCounterChangeNode::INCREMENT);
+ }
+ }
+ }
+
+ if (requiresListItemIncrement && !hasListItemIncrement) {
+ RefPtr<nsAtom> atom = nsGkAtoms::list_item;
+ // We use a magic value here to signal to SetScope() that it should
+ // set the value to -1 or 1 depending on if the scope is reversed()
+ // or not.
+ auto listItemIncrement = nsStyleContent::CounterPair{
+ {StyleAtom(atom.forget())}, std::numeric_limits<int32_t>::min()};
+ dirty |= AddCounterChangeNode(
+ *this, aFrame, styleContent->mCounterIncrement.Length(),
+ listItemIncrement, nsCounterChangeNode::INCREMENT);
+ }
+
+ {
+ int32_t i = 0;
+ for (const auto& pair : styleContent->mCounterSet.AsSpan()) {
+ dirty |= AddCounterChangeNode(*this, aFrame, i++, pair,
+ nsCounterChangeNode::SET);
+ }
+ }
+ return dirty;
+}
+
+nsCounterList* nsCounterManager::GetOrCreateCounterList(nsAtom* aCounterName) {
+ MOZ_ASSERT(aCounterName);
+ return mNames.GetOrInsertNew(aCounterName, aCounterName, mScope);
+}
+
+nsCounterList* nsCounterManager::GetCounterList(nsAtom* aCounterName) {
+ MOZ_ASSERT(aCounterName);
+ return mNames.Get(aCounterName);
+}
+
+void nsCounterManager::RecalcAll() {
+ for (const auto& list : mNames.Values()) {
+ if (list->IsDirty()) {
+ list->RecalcAll();
+ }
+ }
+}
+
+void nsCounterManager::SetAllDirty() {
+ for (const auto& list : mNames.Values()) {
+ list->SetDirty();
+ }
+}
+
+bool nsCounterManager::DestroyNodesFor(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->HasAnyStateBits(NS_FRAME_HAS_CSS_COUNTER_STYLE),
+ "why call me?");
+ bool destroyedAny = false;
+ for (const auto& list : mNames.Values()) {
+ if (list->DestroyNodesFor(aFrame)) {
+ destroyedAny = true;
+ list->SetDirty();
+ }
+ }
+ return destroyedAny;
+}
+
+#ifdef ACCESSIBILITY
+bool nsCounterManager::GetFirstCounterValueForFrame(
+ nsIFrame* aFrame, CounterValue& aOrdinal) const {
+ if (const auto* list = mNames.Get(nsGkAtoms::list_item)) {
+ for (nsCounterNode* n = list->GetFirstNodeFor(aFrame);
+ n && n->mPseudoFrame == aFrame; n = list->Next(n)) {
+ if (n->mType == nsCounterNode::USE) {
+ aOrdinal = n->mValueAfter;
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+void nsCounterManager::Dump() const {
+ printf("\n\nCounter Manager Lists:\n");
+ for (const auto& entry : mNames) {
+ printf("Counter named \"%s\":\n", nsAtomCString(entry.GetKey()).get());
+
+ nsCounterList* list = entry.GetWeak();
+ int32_t i = 0;
+ for (nsCounterNode* node = list->First(); node; node = list->Next(node)) {
+ const char* types[] = {"RESET", "INCREMENT", "SET", "USE"};
+ printf(
+ " Node #%d @%p frame=%p index=%d type=%s valAfter=%d\n"
+ " scope-start=%p scope-prev=%p",
+ i++, (void*)node, (void*)node->mPseudoFrame, node->mContentIndex,
+ types[node->mType], node->mValueAfter, (void*)node->mScopeStart,
+ (void*)node->mScopePrev);
+ if (node->mType == nsCounterNode::USE) {
+ nsAutoString text;
+ node->UseNode()->GetText(text);
+ printf(" text=%s", NS_ConvertUTF16toUTF8(text).get());
+ }
+ printf("\n");
+ }
+ }
+ printf("\n\n");
+}
+#endif
diff --git a/layout/base/nsCounterManager.h b/layout/base/nsCounterManager.h
new file mode 100644
index 0000000000..db40e64442
--- /dev/null
+++ b/layout/base/nsCounterManager.h
@@ -0,0 +1,350 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+// vim:cindent:ai:sw=4:ts=4:et:
+/* 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 CSS counters (for numbering things) */
+
+#ifndef nsCounterManager_h_
+#define nsCounterManager_h_
+
+#include "mozilla/Attributes.h"
+#include "nsGenConList.h"
+#include "nsClassHashtable.h"
+#include "mozilla/Likely.h"
+#include "CounterStyleManager.h"
+
+class nsCounterList;
+struct nsCounterUseNode;
+struct nsCounterChangeNode;
+
+namespace mozilla {
+
+class ContainStyleScope;
+
+} // namespace mozilla
+
+struct nsCounterNode : public nsGenConNode {
+ enum Type {
+ RESET, // a "counter number" pair in 'counter-reset'
+ INCREMENT, // a "counter number" pair in 'counter-increment'
+ SET, // a "counter number" pair in 'counter-set'
+ USE // counter() or counters() in 'content'
+ };
+
+ Type mType;
+
+ // Counter value after this node
+ int32_t mValueAfter = 0;
+
+ // mScopeStart points to the node (usually a RESET, but not in the
+ // case of an implied 'counter-reset') that created the scope for
+ // this element (for a RESET, its outer scope, i.e., the one it is
+ // inside rather than the one it creates).
+
+ // May be null for all types, but only when mScopePrev is also null.
+ // Being null for a non-RESET means that it is an implied
+ // 'counter-reset'. Being null for a RESET means it has no outer
+ // scope.
+ nsCounterNode* mScopeStart = nullptr;
+
+ // mScopePrev points to the previous node that is in the same scope,
+ // or for a RESET, the previous node in the scope outside of the
+ // reset.
+
+ // May be null for all types, but only when mScopeStart is also
+ // null. Following the mScopePrev links will eventually lead to
+ // mScopeStart. Being null for a non-RESET means that it is an
+ // implied 'counter-reset'. Being null for a RESET means it has no
+ // outer scope.
+ nsCounterNode* mScopePrev = nullptr;
+
+ // Whether or not this node's scope crosses `contain: style` boundaries.
+ // This can happen for USE nodes that come before any other types of
+ // nodes in a `contain: style` boundary's list.
+ bool mCrossesContainStyleBoundaries = false;
+
+ inline nsCounterUseNode* UseNode();
+ inline nsCounterChangeNode* ChangeNode();
+
+ // For RESET, INCREMENT and SET nodes, aPseudoFrame need not be a
+ // pseudo-element, and aContentIndex represents the index within the
+ // 'counter-reset', 'counter-increment' or 'counter-set' property
+ // instead of within the 'content' property but offset to ensure
+ // that (reset, increment, set, use) sort in that order.
+ // It is zero for legacy bullet USE counter nodes.
+ // (This slight weirdness allows sharing a lot of code with 'quotes'.)
+ nsCounterNode(int32_t aContentIndex, Type aType)
+ : nsGenConNode(aContentIndex), mType(aType) {}
+
+ // to avoid virtual function calls in the common case
+ inline void Calc(nsCounterList* aList, bool aNotify);
+
+ // Is this a RESET node for a content-based (i.e. without a start value)
+ // reversed() counter?
+ inline bool IsContentBasedReset();
+
+ // Is this a RESET node for a reversed() counter?
+ inline bool IsReversed();
+
+ // Is this an INCREMENT node that needs to be initialized to -1 or 1
+ // depending on if our scope is reversed() or not?
+ inline bool IsUnitializedIncrementNode();
+};
+
+struct nsCounterUseNode : public nsCounterNode {
+ mozilla::CounterStylePtr mCounterStyle;
+ nsString mSeparator;
+
+ // false for counter(), true for counters()
+ bool mAllCounters = false;
+
+ bool mForLegacyBullet = false;
+
+ enum ForLegacyBullet { ForLegacyBullet };
+ nsCounterUseNode(enum ForLegacyBullet, mozilla::CounterStylePtr aCounterStyle)
+ : nsCounterNode(0, USE),
+ mCounterStyle(std::move(aCounterStyle)),
+ mForLegacyBullet(true) {}
+
+ // args go directly to member variables here and of nsGenConNode
+ nsCounterUseNode(mozilla::CounterStylePtr aCounterStyle, nsString aSeparator,
+ uint32_t aContentIndex, bool aAllCounters)
+ : nsCounterNode(aContentIndex, USE),
+ mCounterStyle(std::move(aCounterStyle)),
+ mSeparator(std::move(aSeparator)),
+ mAllCounters(aAllCounters) {
+ NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
+ }
+
+ bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame) override;
+
+ // assign the correct |mValueAfter| value to a node that has been inserted,
+ // and update the value of the text node, notifying if `aNotify` is true.
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsCounterList* aList, bool aNotify);
+
+ // The text that should be displayed for this counter.
+ void GetText(nsString& aResult);
+ void GetText(mozilla::WritingMode aWM, mozilla::CounterStyle* aStyle,
+ nsString& aResult);
+};
+
+struct nsCounterChangeNode : public nsCounterNode {
+ // |aPseudoFrame| is not necessarily a pseudo-element's frame, but
+ // since it is for every other subclass of nsGenConNode, we follow
+ // the naming convention here.
+ // |aPropIndex| is the index of the value within the list in the
+ // 'counter-increment', 'counter-reset' or 'counter-set' property.
+ nsCounterChangeNode(nsIFrame* aPseudoFrame, nsCounterNode::Type aChangeType,
+ int32_t aChangeValue, int32_t aPropIndex,
+ bool aIsReversed)
+ : nsCounterNode( // Fake a content index for resets, increments and sets
+ // that comes before all the real content, with
+ // the resets first, in order, and then the increments
+ // and then the sets.
+ aPropIndex + (aChangeType == RESET ? (INT32_MIN)
+ : (aChangeType == INCREMENT
+ ? ((INT32_MIN / 3) * 2)
+ : INT32_MIN / 3)),
+ aChangeType),
+ mChangeValue(aChangeValue),
+ mIsReversed(aIsReversed),
+ mSeenSetNode(false) {
+ NS_ASSERTION(aPropIndex >= 0, "out of range");
+ NS_ASSERTION(
+ aChangeType == INCREMENT || aChangeType == SET || aChangeType == RESET,
+ "bad type");
+ mPseudoFrame = aPseudoFrame;
+ CheckFrameAssertions();
+ }
+
+ // assign the correct |mValueAfter| value to a node that has been inserted
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsCounterList* aList);
+
+ // The numeric value of the INCREMENT, SET or RESET.
+ // Note: numeric_limits<int32_t>::min() is used for content-based reversed()
+ // RESET nodes, and temporarily on INCREMENT nodes to signal that it should be
+ // initialized to -1 or 1 depending on if the scope is reversed() or not.
+ int32_t mChangeValue;
+
+ // True if the counter is reversed(). Only used on RESET nodes.
+ bool mIsReversed : 1;
+ // True if we've seen a SET node during the initialization of
+ // an IsContentBasedReset() node; always false on other nodes.
+ bool mSeenSetNode : 1;
+};
+
+inline nsCounterUseNode* nsCounterNode::UseNode() {
+ NS_ASSERTION(mType == USE, "wrong type");
+ return static_cast<nsCounterUseNode*>(this);
+}
+
+inline nsCounterChangeNode* nsCounterNode::ChangeNode() {
+ MOZ_ASSERT(mType == INCREMENT || mType == SET || mType == RESET);
+ return static_cast<nsCounterChangeNode*>(this);
+}
+
+inline void nsCounterNode::Calc(nsCounterList* aList, bool aNotify) {
+ if (mType == USE)
+ UseNode()->Calc(aList, aNotify);
+ else
+ ChangeNode()->Calc(aList);
+}
+
+inline bool nsCounterNode::IsContentBasedReset() {
+ return mType == RESET &&
+ ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min();
+}
+
+inline bool nsCounterNode::IsReversed() {
+ return mType == RESET && ChangeNode()->mIsReversed;
+}
+
+inline bool nsCounterNode::IsUnitializedIncrementNode() {
+ return mType == INCREMENT &&
+ ChangeNode()->mChangeValue == std::numeric_limits<int32_t>::min();
+}
+
+class nsCounterList : public nsGenConList {
+ public:
+ nsCounterList(nsAtom* aCounterName, mozilla::ContainStyleScope* aScope)
+ : mCounterName(aCounterName), mScope(aScope) {
+ MOZ_ASSERT(aScope);
+ }
+
+ // Return the first node for aFrame on this list, or nullptr.
+ nsCounterNode* GetFirstNodeFor(nsIFrame* aFrame) const {
+ return static_cast<nsCounterNode*>(nsGenConList::GetFirstNodeFor(aFrame));
+ }
+
+ void Insert(nsCounterNode* aNode) {
+ nsGenConList::Insert(aNode);
+ // Don't SetScope if we're dirty -- we'll reset all the scopes anyway,
+ // and we can't usefully compute scopes right now.
+ if (MOZ_LIKELY(!IsDirty())) {
+ SetScope(aNode);
+ }
+ }
+
+ nsCounterNode* First() {
+ return static_cast<nsCounterNode*>(mList.getFirst());
+ }
+
+ static nsCounterNode* Next(nsCounterNode* aNode) {
+ return static_cast<nsCounterNode*>(nsGenConList::Next(aNode));
+ }
+ static nsCounterNode* Prev(nsCounterNode* aNode) {
+ return static_cast<nsCounterNode*>(nsGenConList::Prev(aNode));
+ }
+
+ static int32_t ValueBefore(nsCounterNode* aNode) {
+ if (!aNode->mScopePrev) {
+ return 0;
+ }
+
+ if (aNode->mType != nsCounterNode::USE &&
+ aNode->mScopePrev->mCrossesContainStyleBoundaries) {
+ return 0;
+ }
+
+ return aNode->mScopePrev->mValueAfter;
+ }
+
+ // Correctly set |aNode->mScopeStart| and |aNode->mScopePrev|
+ void SetScope(nsCounterNode* aNode);
+
+ // Recalculate |mScopeStart|, |mScopePrev|, and |mValueAfter| for
+ // all nodes and update text in text content nodes.
+ void RecalcAll();
+
+ bool IsDirty() const;
+ void SetDirty();
+ bool IsRecalculatingAll() const { return mRecalculatingAll; }
+
+ private:
+ bool SetScopeByWalkingBackwardThroughList(
+ nsCounterNode* aNodeToSetScopeFor, const nsIContent* aNodeContent,
+ nsCounterNode* aNodeToBeginLookingAt);
+
+ RefPtr<nsAtom> mCounterName;
+ mozilla::ContainStyleScope* mScope;
+ bool mRecalculatingAll = false;
+};
+
+/**
+ * The counter manager maintains an |nsCounterList| for each named
+ * counter to keep track of all scopes with that name.
+ */
+class nsCounterManager {
+ public:
+ explicit nsCounterManager(mozilla::ContainStyleScope* scope)
+ : mScope(scope) {}
+
+ // Returns true if dirty
+ bool AddCounterChanges(nsIFrame* aFrame);
+
+ // Gets the appropriate counter list, creating it if necessary.
+ // Guaranteed to return non-null. (Uses an infallible hashtable API.)
+ nsCounterList* GetOrCreateCounterList(nsAtom* aCounterName);
+
+ // Gets the appropriate counter list, returning null if it doesn't exist.
+ nsCounterList* GetCounterList(nsAtom* aCounterName);
+
+ // Clean up data in any dirty counter lists.
+ void RecalcAll();
+
+ // Set all counter lists dirty
+ void SetAllDirty();
+
+ // Destroy nodes for the frame in any lists, and return whether any
+ // nodes were destroyed.
+ bool DestroyNodesFor(nsIFrame* aFrame);
+
+ // Clear all data.
+ void Clear() { mNames.Clear(); }
+
+#ifdef ACCESSIBILITY
+ // Set |aOrdinal| to the first used counter value for the given frame and
+ // return true. If no USE node for the given frame can be found, return false
+ // and do not change the value of |aOrdinal|.
+ bool GetFirstCounterValueForFrame(nsIFrame* aFrame,
+ mozilla::CounterValue& aOrdinal) const;
+#endif
+
+#if defined(DEBUG) || defined(MOZ_LAYOUT_DEBUGGER)
+ void Dump() const;
+#endif
+
+ static int32_t IncrementCounter(int32_t aOldValue, int32_t aIncrement) {
+ // Addition of unsigned values is defined to be arithmetic
+ // modulo 2^bits (C++ 2011, 3.9.1 [basic.fundamental], clause 4);
+ // addition of signed values is undefined (and clang does
+ // something very strange if we use it here). Likewise integral
+ // conversion from signed to unsigned is also defined as modulo
+ // 2^bits (C++ 2011, 4.7 [conv.integral], clause 2); conversion
+ // from unsigned to signed is however undefined (ibid., clause 3),
+ // but to do what we want we must nonetheless depend on that
+ // small piece of undefined behavior.
+ int32_t newValue = int32_t(uint32_t(aOldValue) + uint32_t(aIncrement));
+ // The CSS Working Group resolved that a counter-increment that
+ // exceeds internal limits should not increment at all.
+ // http://lists.w3.org/Archives/Public/www-style/2013Feb/0392.html
+ // (This means, for example, that if aIncrement is 5, the
+ // counter will get stuck at the largest multiple of 5 less than
+ // the maximum 32-bit integer.)
+ if ((aIncrement > 0) != (newValue > aOldValue)) {
+ newValue = aOldValue;
+ }
+ return newValue;
+ }
+
+ private:
+ mozilla::ContainStyleScope* mScope;
+ nsClassHashtable<nsAtomHashKey, nsCounterList> mNames;
+};
+
+#endif /* nsCounterManager_h_ */
diff --git a/layout/base/nsDocumentViewer.cpp b/layout/base/nsDocumentViewer.cpp
new file mode 100644
index 0000000000..ab61daae87
--- /dev/null
+++ b/layout/base/nsDocumentViewer.cpp
@@ -0,0 +1,3514 @@
+/* -*- 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/. */
+
+/* container for a document and its presentation */
+
+#include "gfxContext.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/Telemetry.h"
+#include "nsThreadUtils.h"
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "nsCRT.h"
+#include "nsFrameSelection.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIContent.h"
+#include "nsIDocumentViewer.h"
+#include "nsIDocumentViewerPrint.h"
+#include "nsIScreen.h"
+#include "mozilla/dom/AutoSuppressEventHandlingAndSuspend.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BeforeUnloadEvent.h"
+#include "mozilla/dom/PopupBlocker.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DocGroup.h"
+#include "mozilla/widget/Screen.h"
+#include "nsPresContext.h"
+#include "nsIFrame.h"
+#include "nsIWritablePropertyBag2.h"
+#include "nsSubDocumentFrame.h"
+#include "nsGenericHTMLElement.h"
+#include "nsStubMutationObserver.h"
+
+#include "nsISelectionListener.h"
+#include "mozilla/dom/Selection.h"
+#include "nsContentUtils.h"
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/DocAccessible.h"
+#endif
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_javascript.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_print.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Try.h"
+
+#include "nsViewManager.h"
+#include "nsView.h"
+
+#include "nsPageSequenceFrame.h"
+#include "nsNetUtil.h"
+#include "nsIDocumentViewerEdit.h"
+#include "mozilla/css/Loader.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsDocShell.h"
+#include "nsIBaseWindow.h"
+#include "nsILayoutHistoryState.h"
+#include "nsCharsetSource.h"
+#include "mozilla/ReflowInput.h"
+#include "nsIImageLoadingContent.h"
+#include "nsCopySupport.h"
+#include "nsXULPopupManager.h"
+
+#include "nsIClipboardHelper.h"
+
+#include "nsPIDOMWindow.h"
+#include "nsGlobalWindowInner.h"
+#include "nsGlobalWindowOuter.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsPIWindowRoot.h"
+#include "nsJSEnvironment.h"
+#include "nsFocusManager.h"
+
+#include "nsIScrollableFrame.h"
+#include "nsStyleSheetService.h"
+#include "nsILoadContext.h"
+#include "mozilla/ThrottledEventQueue.h"
+#include "nsIPromptCollection.h"
+#include "nsIPromptService.h"
+#include "imgIContainer.h" // image animation mode constants
+#include "nsIXULRuntime.h"
+#include "nsSandboxFlags.h"
+
+//--------------------------
+// Printing Include
+//---------------------------
+#ifdef NS_PRINTING
+
+# include "nsIWebBrowserPrint.h"
+
+# include "nsPrintJob.h"
+# include "nsDeviceContextSpecProxy.h"
+
+// Print Options
+# include "nsIPrintSettings.h"
+# include "nsIPrintSettingsService.h"
+# include "nsISimpleEnumerator.h"
+
+#endif // NS_PRINTING
+
+// focus
+#include "nsIDOMEventListener.h"
+#include "nsISelectionController.h"
+
+#include "mozilla/EventDispatcher.h"
+#include "nsISHEntry.h"
+#include "nsISHistory.h"
+#include "nsIWebNavigation.h"
+#include "mozilla/dom/XMLHttpRequestMainThread.h"
+
+// paint forcing
+#include <stdio.h>
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/dom/ScriptLoader.h"
+#include "mozilla/dom/WindowGlobalChild.h"
+
+namespace mozilla {
+namespace dom {
+class PrintPreviewResultInfo;
+} // namespace dom
+} // namespace mozilla
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+using mozilla::layout::RemotePrintJobChild;
+using PrintPreviewResolver =
+ std::function<void(const mozilla::dom::PrintPreviewResultInfo&)>;
+
+//-----------------------------------------------------
+// LOGGING
+#include "LayoutLogging.h"
+#include "mozilla/Logging.h"
+
+extern mozilla::LazyLogModule gPageCacheLog;
+
+#ifdef NS_PRINTING
+mozilla::LazyLogModule gPrintingLog("printing");
+
+# define PR_PL(_p1) MOZ_LOG(gPrintingLog, mozilla::LogLevel::Debug, _p1);
+#endif // NS_PRINTING
+
+#define PRT_YESNO(_p) ((_p) ? "YES" : "NO")
+//-----------------------------------------------------
+
+class nsDocumentViewer;
+
+// a small delegate class used to avoid circular references
+
+class nsDocViewerSelectionListener final : public nsISelectionListener {
+ public:
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // nsISelectionListerner interface
+ NS_DECL_NSISELECTIONLISTENER
+
+ explicit nsDocViewerSelectionListener(nsDocumentViewer* aDocViewer)
+ : mDocViewer(aDocViewer), mSelectionWasCollapsed(true) {}
+
+ void Disconnect() { mDocViewer = nullptr; }
+
+ protected:
+ virtual ~nsDocViewerSelectionListener() = default;
+
+ nsDocumentViewer* mDocViewer;
+ bool mSelectionWasCollapsed;
+};
+
+/** editor Implementation of the FocusListener interface */
+class nsDocViewerFocusListener final : public nsIDOMEventListener {
+ public:
+ explicit nsDocViewerFocusListener(nsDocumentViewer* aDocViewer)
+ : mDocViewer(aDocViewer) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIDOMEVENTLISTENER
+
+ void Disconnect() { mDocViewer = nullptr; }
+
+ protected:
+ virtual ~nsDocViewerFocusListener() = default;
+
+ nsDocumentViewer* mDocViewer;
+};
+
+namespace viewer_detail {
+
+/**
+ * Mutation observer for use until we hand ourselves over to our SHEntry.
+ */
+class BFCachePreventionObserver final : public nsStubMutationObserver {
+ public:
+ explicit BFCachePreventionObserver(Document* aDocument)
+ : mDocument(aDocument) {}
+
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_NODEWILLBEDESTROYED
+
+ // Stop observing the document.
+ void Disconnect();
+
+ private:
+ ~BFCachePreventionObserver() = default;
+
+ // Helper for the work that needs to happen when mutations happen.
+ void MutationHappened();
+
+ Document* mDocument; // Weak; we get notified if it dies
+};
+
+NS_IMPL_ISUPPORTS(BFCachePreventionObserver, nsIMutationObserver)
+
+void BFCachePreventionObserver::CharacterDataChanged(
+ nsIContent* aContent, const CharacterDataChangeInfo&) {
+ if (aContent->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+ MutationHappened();
+}
+
+void BFCachePreventionObserver::AttributeChanged(Element* aElement,
+ int32_t aNameSpaceID,
+ nsAtom* aAttribute,
+ int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (aElement->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+ MutationHappened();
+}
+
+void BFCachePreventionObserver::ContentAppended(nsIContent* aFirstNewContent) {
+ if (aFirstNewContent->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+ MutationHappened();
+}
+
+void BFCachePreventionObserver::ContentInserted(nsIContent* aChild) {
+ if (aChild->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+ MutationHappened();
+}
+
+void BFCachePreventionObserver::ContentRemoved(nsIContent* aChild,
+ nsIContent* aPreviousSibling) {
+ if (aChild->IsInNativeAnonymousSubtree()) {
+ return;
+ }
+ MutationHappened();
+}
+
+void BFCachePreventionObserver::NodeWillBeDestroyed(nsINode* aNode) {
+ mDocument = nullptr;
+}
+
+void BFCachePreventionObserver::Disconnect() {
+ if (mDocument) {
+ mDocument->RemoveMutationObserver(this);
+ // It will no longer tell us when it goes away, so make sure we're
+ // not holding a dangling ref.
+ mDocument = nullptr;
+ }
+}
+
+void BFCachePreventionObserver::MutationHappened() {
+ MOZ_ASSERT(
+ mDocument,
+ "How can we not have a document but be getting notified for mutations?");
+ mDocument->DisallowBFCaching();
+ Disconnect();
+}
+
+} // namespace viewer_detail
+
+using viewer_detail::BFCachePreventionObserver;
+
+//-------------------------------------------------------------
+class nsDocumentViewer final : public nsIDocumentViewer,
+ public nsIDocumentViewerEdit,
+ public nsIDocumentViewerPrint
+#ifdef NS_PRINTING
+ ,
+ public nsIWebBrowserPrint
+#endif
+
+{
+ friend class nsDocViewerSelectionListener;
+ friend class nsPagePrintTimer;
+ friend class nsPrintJob;
+
+ public:
+ nsDocumentViewer();
+
+ // nsISupports interface...
+ NS_DECL_ISUPPORTS
+
+ // nsIDocumentViewer interface...
+ NS_DECL_NSIDOCUMENTVIEWER
+
+ // nsIDocumentViewerEdit
+ NS_DECL_NSIDOCUMENTVIEWEREDIT
+
+#ifdef NS_PRINTING
+ // nsIWebBrowserPrint
+ NS_DECL_NSIWEBBROWSERPRINT
+#endif
+
+ // nsIDocumentViewerPrint Printing Methods
+ NS_DECL_NSIDOCUMENTVIEWERPRINT
+
+ protected:
+ virtual ~nsDocumentViewer();
+
+ private:
+ /**
+ * Creates a view manager, root view, and widget for the root view, setting
+ * mViewManager and mWindow.
+ * @param aSize the initial size in appunits
+ * @param aContainerView the container view to hook our root view up
+ * to as a child, or null if this will be the root view manager
+ */
+ nsresult MakeWindow(const nsSize& aSize, nsView* aContainerView);
+
+ /**
+ * Create our device context
+ */
+ nsresult CreateDeviceContext(nsView* aContainerView);
+
+ /**
+ * If aDoCreation is true, this creates the device context, creates a
+ * prescontext if necessary, and calls MakeWindow.
+ *
+ * If aForceSetNewDocument is false, then SetNewDocument won't be
+ * called if the window's current document is already mDocument.
+ */
+ nsresult InitInternal(nsIWidget* aParentWidget, nsISupports* aState,
+ mozilla::dom::WindowGlobalChild* aActor,
+ const nsIntRect& aBounds, bool aDoCreation,
+ bool aNeedMakeCX = true,
+ bool aForceSetNewDocument = true);
+ /**
+ * @param aDoInitialReflow set to true if you want to kick off the initial
+ * reflow
+ */
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ nsresult InitPresentationStuff(bool aDoInitialReflow);
+
+ already_AddRefed<nsINode> GetPopupNode();
+ already_AddRefed<nsINode> GetPopupLinkNode();
+ already_AddRefed<nsIImageLoadingContent> GetPopupImageNode();
+
+ void PrepareToStartLoad(void);
+
+ nsresult SyncParentSubDocMap();
+
+ void RemoveFocusListener();
+ void ReinitializeFocusListener();
+
+ mozilla::dom::Selection* GetDocumentSelection();
+
+ void DestroyPresShell();
+ void DestroyPresContext();
+
+ void InvalidatePotentialSubDocDisplayItem();
+
+ // Whether we should attach to the top level widget. This is true if we
+ // are sharing/recycling a single base widget and not creating multiple
+ // child widgets.
+ bool ShouldAttachToTopLevel();
+
+ std::tuple<const nsIFrame*, int32_t> GetCurrentSheetFrameAndNumber() const;
+
+ protected:
+ // Returns the current viewmanager. Might be null.
+ nsViewManager* GetViewManager();
+
+ void DetachFromTopLevelWidget();
+
+ // IMPORTANT: The ownership implicit in the following member
+ // variables has been explicitly checked and set using nsCOMPtr
+ // for owning pointers and raw COM interface pointers for weak
+ // (ie, non owning) references. If you add any members to this
+ // class, please make the ownership explicit (pinkerton, scc).
+
+ WeakPtr<nsDocShell> mContainer; // it owns me!
+ RefPtr<nsDeviceContext> mDeviceContext; // We create and own this baby
+
+ // the following six items are explicitly in this order
+ // so they will be destroyed in the reverse order (pinkerton, scc)
+ nsCOMPtr<Document> mDocument;
+ nsCOMPtr<nsIWidget> mWindow; // may be null
+ RefPtr<nsViewManager> mViewManager;
+ RefPtr<nsPresContext> mPresContext;
+ RefPtr<PresShell> mPresShell;
+
+ RefPtr<nsDocViewerSelectionListener> mSelectionListener;
+ RefPtr<nsDocViewerFocusListener> mFocusListener;
+
+ nsCOMPtr<nsIDocumentViewer> mPreviousViewer;
+ nsCOMPtr<nsISHEntry> mSHEntry;
+ // Observer that will prevent bfcaching if it gets notified. This
+ // is non-null precisely when mSHEntry is non-null.
+ RefPtr<BFCachePreventionObserver> mBFCachePreventionObserver;
+
+ nsIWidget* mParentWidget; // purposely won't be ref counted. May be null
+ bool mAttachedToParent; // view is attached to the parent widget
+
+ nsIntRect mBounds;
+
+ int16_t mNumURLStarts;
+ int16_t mDestroyBlockedCount;
+
+ unsigned mStopped : 1;
+ unsigned mLoaded : 1;
+ unsigned mDeferredWindowClose : 1;
+ // document management data
+ // these items are specific to markup documents (html and xml)
+ // may consider splitting these out into a subclass
+ unsigned mIsSticky : 1;
+ unsigned mInPermitUnload : 1;
+ unsigned mInPermitUnloadPrompt : 1;
+
+#ifdef NS_PRINTING
+ unsigned mClosingWhilePrinting : 1;
+
+# if NS_PRINT_PREVIEW
+ RefPtr<nsPrintJob> mPrintJob;
+# endif // NS_PRINT_PREVIEW
+
+#endif // NS_PRINTING
+
+ /* character set member data */
+ int32_t mReloadEncodingSource;
+ const Encoding* mReloadEncoding;
+
+ bool mIsPageMode;
+ bool mInitializedForPrintPreview;
+ bool mHidden;
+};
+
+class nsDocumentShownDispatcher : public Runnable {
+ public:
+ explicit nsDocumentShownDispatcher(nsCOMPtr<Document> aDocument)
+ : Runnable("nsDocumentShownDispatcher"), mDocument(aDocument) {}
+
+ NS_IMETHOD Run() override;
+
+ private:
+ nsCOMPtr<Document> mDocument;
+};
+
+//------------------------------------------------------------------
+// nsDocumentViewer
+//------------------------------------------------------------------
+
+//------------------------------------------------------------------
+already_AddRefed<nsIDocumentViewer> NS_NewDocumentViewer() {
+ RefPtr<nsDocumentViewer> viewer = new nsDocumentViewer();
+ return viewer.forget();
+}
+
+void nsDocumentViewer::PrepareToStartLoad() {
+ MOZ_DIAGNOSTIC_ASSERT(!GetIsPrintPreview(),
+ "Print preview tab should never navigate");
+
+ mStopped = false;
+ mLoaded = false;
+ mAttachedToParent = false;
+ mDeferredWindowClose = false;
+
+#ifdef NS_PRINTING
+ mClosingWhilePrinting = false;
+
+ // Make sure we have destroyed it and cleared the data member
+ if (mPrintJob) {
+ mPrintJob->Destroy();
+ mPrintJob = nullptr;
+ }
+
+#endif // NS_PRINTING
+}
+
+nsDocumentViewer::nsDocumentViewer()
+ : mParentWidget(nullptr),
+ mAttachedToParent(false),
+ mNumURLStarts(0),
+ mDestroyBlockedCount(0),
+ mStopped(false),
+ mLoaded(false),
+ mDeferredWindowClose(false),
+ mIsSticky(true),
+ mInPermitUnload(false),
+ mInPermitUnloadPrompt(false),
+#ifdef NS_PRINTING
+ mClosingWhilePrinting(false),
+#endif // NS_PRINTING
+ mReloadEncodingSource(kCharsetUninitialized),
+ mReloadEncoding(nullptr),
+ mIsPageMode(false),
+ mInitializedForPrintPreview(false),
+ mHidden(false) {
+ PrepareToStartLoad();
+}
+
+NS_IMPL_ADDREF(nsDocumentViewer)
+NS_IMPL_RELEASE(nsDocumentViewer)
+
+NS_INTERFACE_MAP_BEGIN(nsDocumentViewer)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentViewer)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerEdit)
+ NS_INTERFACE_MAP_ENTRY(nsIDocumentViewerPrint)
+ NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentViewer)
+#ifdef NS_PRINTING
+ NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPrint)
+#endif
+NS_INTERFACE_MAP_END
+
+nsDocumentViewer::~nsDocumentViewer() {
+ if (mDocument) {
+ Close(nullptr);
+ mDocument->Destroy();
+ }
+
+#ifdef NS_PRINTING
+ if (mPrintJob) {
+ mPrintJob->Destroy();
+ mPrintJob = nullptr;
+ }
+#endif
+
+ MOZ_RELEASE_ASSERT(mDestroyBlockedCount == 0);
+ NS_ASSERTION(!mPresShell && !mPresContext,
+ "User did not call nsIDocumentViewer::Destroy");
+ if (mPresShell || mPresContext) {
+ // Make sure we don't hand out a reference to the content viewer to
+ // the SHEntry!
+ mSHEntry = nullptr;
+
+ Destroy();
+ }
+
+ if (mSelectionListener) {
+ mSelectionListener->Disconnect();
+ }
+
+ RemoveFocusListener();
+
+ // XXX(?) Revoke pending invalidate events
+}
+
+/*
+ * This method is called by the Document Loader once a document has
+ * been created for a particular data stream... The content viewer
+ * must cache this document for later use when Init(...) is called.
+ *
+ * This method is also called when an out of band document.write() happens.
+ * In that case, the document passed in is the same as the previous document.
+ */
+/* virtual */
+void nsDocumentViewer::LoadStart(Document* aDocument) {
+ MOZ_ASSERT(aDocument);
+
+ if (!mDocument) {
+ mDocument = aDocument;
+ }
+}
+
+void nsDocumentViewer::RemoveFocusListener() {
+ if (RefPtr<nsDocViewerFocusListener> oldListener =
+ std::move(mFocusListener)) {
+ oldListener->Disconnect();
+ if (mDocument) {
+ mDocument->RemoveEventListener(u"focus"_ns, oldListener, false);
+ mDocument->RemoveEventListener(u"blur"_ns, oldListener, false);
+ }
+ }
+}
+
+void nsDocumentViewer::ReinitializeFocusListener() {
+ RemoveFocusListener();
+ mFocusListener = new nsDocViewerFocusListener(this);
+ if (mDocument) {
+ mDocument->AddEventListener(u"focus"_ns, mFocusListener, false, false);
+ mDocument->AddEventListener(u"blur"_ns, mFocusListener, false, false);
+ }
+}
+
+nsresult nsDocumentViewer::SyncParentSubDocMap() {
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (!docShell) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
+ if (!mDocument || !pwin) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<Element> element = pwin->GetFrameElementInternal();
+ if (!element) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parent;
+ docShell->GetInProcessParent(getter_AddRefs(parent));
+
+ nsCOMPtr<nsPIDOMWindowOuter> parent_win =
+ parent ? parent->GetWindow() : nullptr;
+ if (!parent_win) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<Document> parent_doc = parent_win->GetDoc();
+ if (!parent_doc) {
+ return NS_OK;
+ }
+
+ if (mDocument && parent_doc->GetSubDocumentFor(element) != mDocument &&
+ parent_doc->EventHandlingSuppressed()) {
+ mDocument->SuppressEventHandling(parent_doc->EventHandlingSuppressed());
+ }
+ return parent_doc->SetSubDocumentFor(element, mDocument);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetContainer(nsIDocShell* aContainer) {
+ mContainer = static_cast<nsDocShell*>(aContainer);
+
+ // We're loading a new document into the window where this document
+ // viewer lives, sync the parent document's frame element -> sub
+ // document map
+
+ return SyncParentSubDocMap();
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetContainer(nsIDocShell** aResult) {
+ NS_ENSURE_ARG_POINTER(aResult);
+
+ nsCOMPtr<nsIDocShell> container(mContainer);
+ container.swap(*aResult);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Init(nsIWidget* aParentWidget, const nsIntRect& aBounds,
+ WindowGlobalChild* aActor) {
+ return InitInternal(aParentWidget, nullptr, aActor, aBounds, true);
+}
+
+nsresult nsDocumentViewer::InitPresentationStuff(bool aDoInitialReflow) {
+ // We assert this because initializing the pres shell could otherwise cause
+ // re-entrancy into nsDocumentViewer methods, which might cause a different
+ // pres shell to be created. Callers of InitPresentationStuff should ensure
+ // the call is appropriately bounded by an nsAutoScriptBlocker to decide
+ // when it is safe for these re-entrant calls to be made.
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "InitPresentationStuff must only be called when scripts are "
+ "blocked");
+
+#ifdef NS_PRINTING
+ // When getting printed, either for print or print preview, the print job
+ // takes care of setting up the presentation of the document.
+ if (mPrintJob) {
+ return NS_OK;
+ }
+#endif
+
+ NS_ASSERTION(!mPresShell, "Someone should have destroyed the presshell!");
+
+ // Now make the shell for the document
+ nsCOMPtr<Document> doc = mDocument;
+ RefPtr<nsPresContext> presContext = mPresContext;
+ RefPtr<nsViewManager> viewManager = mViewManager;
+ mPresShell = doc->CreatePresShell(presContext, viewManager);
+ if (!mPresShell) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aDoInitialReflow) {
+ // Since Initialize() will create frames for *all* items
+ // that are currently in the document tree, we need to flush
+ // any pending notifications to prevent the content sink from
+ // duplicating layout frames for content it has added to the tree
+ // but hasn't notified the document about. (Bug 154018)
+ //
+ // Note that we are flushing before we add mPresShell as an observer
+ // to avoid bogus notifications.
+ mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
+ }
+
+ mPresShell->BeginObservingDocument();
+
+ // Initialize our view manager
+
+ {
+ int32_t p2a = mPresContext->AppUnitsPerDevPixel();
+ MOZ_ASSERT(
+ p2a ==
+ mPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom());
+
+ nscoord width = p2a * mBounds.width;
+ nscoord height = p2a * mBounds.height;
+
+ mViewManager->SetWindowDimensions(width, height);
+ mPresContext->SetVisibleArea(nsRect(0, 0, width, height));
+ // We rely on the default zoom not being initialized until here.
+ mPresContext->RecomputeBrowsingContextDependentData();
+ }
+
+ if (mWindow && mDocument->IsTopLevelContentDocument()) {
+ // Set initial safe area insets
+ ScreenIntMargin windowSafeAreaInsets;
+ LayoutDeviceIntRect windowRect = mWindow->GetScreenBounds();
+ nsCOMPtr<nsIScreen> screen = mWindow->GetWidgetScreen();
+ if (screen) {
+ windowSafeAreaInsets = nsContentUtils::GetWindowSafeAreaInsets(
+ screen, mWindow->GetSafeAreaInsets(), windowRect);
+ }
+
+ mPresContext->SetSafeAreaInsets(windowSafeAreaInsets);
+ }
+
+ if (aDoInitialReflow) {
+ RefPtr<PresShell> presShell = mPresShell;
+ // Initial reflow
+ presShell->Initialize();
+ }
+
+ // now register ourselves as a selection listener, so that we get
+ // called when the selection changes in the window
+ if (!mSelectionListener) {
+ mSelectionListener = new nsDocViewerSelectionListener(this);
+ }
+
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ selection->AddSelectionListener(mSelectionListener);
+
+ ReinitializeFocusListener();
+
+ if (aDoInitialReflow && mDocument) {
+ nsCOMPtr<Document> document = mDocument;
+ document->ScrollToRef();
+ }
+
+ return NS_OK;
+}
+
+static nsPresContext* CreatePresContext(Document* aDocument,
+ nsPresContext::nsPresContextType aType,
+ nsView* aContainerView) {
+ if (aContainerView) {
+ return new nsPresContext(aDocument, aType);
+ }
+ return new nsRootPresContext(aDocument, aType);
+}
+
+//-----------------------------------------------
+// This method can be used to initial the "presentation"
+// The aDoCreation indicates whether it should create
+// all the new objects or just initialize the existing ones
+nsresult nsDocumentViewer::InitInternal(
+ nsIWidget* aParentWidget, nsISupports* aState, WindowGlobalChild* aActor,
+ const nsIntRect& aBounds, bool aDoCreation, bool aNeedMakeCX /*= true*/,
+ bool aForceSetNewDocument /* = true*/) {
+ // We don't want any scripts to run here. That can cause flushing,
+ // which can cause reentry into initialization of this document viewer,
+ // which would be disastrous.
+ nsAutoScriptBlocker blockScripts;
+
+ mParentWidget = aParentWidget; // not ref counted
+ mBounds = aBounds;
+
+ nsresult rv = NS_OK;
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NULL_POINTER);
+
+ nsView* containerView = FindContainerView();
+
+ bool makeCX = false;
+ if (aDoCreation) {
+ nsresult rv = CreateDeviceContext(containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // XXXbz this is a nasty hack to do with the fact that we create
+ // presentations both in Init() and in Show()... Ideally we would only do
+ // it in one place (Show()) and require that callers call init(), open(),
+ // show() in that order or something.
+ if (!mPresContext &&
+ (aParentWidget || containerView || mDocument->IsBeingUsedAsImage() ||
+ (mDocument->GetDisplayDocument() &&
+ mDocument->GetDisplayDocument()->GetPresShell()))) {
+ // Create presentation context
+ if (mIsPageMode) {
+ // Presentation context already created in SetPageModeForTesting which
+ // is calling this method
+ } else {
+ mPresContext = CreatePresContext(
+ mDocument, nsPresContext::eContext_Galley, containerView);
+ }
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+
+ nsresult rv = mPresContext->Init(mDeviceContext);
+ if (NS_FAILED(rv)) {
+ mPresContext = nullptr;
+ return rv;
+ }
+
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ makeCX = !GetIsPrintPreview() &&
+ aNeedMakeCX; // needs to be true except when we are already in
+ // PP or we are enabling/disabling paginated mode.
+#else
+ makeCX = true;
+#endif
+ }
+
+ if (mPresContext) {
+ // Create the ViewManager and Root View...
+
+ // We must do this before we tell the script global object about
+ // this new document since doing that will cause us to re-enter
+ // into nsSubDocumentFrame code through reflows caused by
+ // FlushPendingNotifications() calls down the road...
+
+ rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(aBounds.width),
+ mPresContext->DevPixelsToAppUnits(aBounds.height)),
+ containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+ Hide();
+
+#ifdef NS_PRINT_PREVIEW
+ if (mIsPageMode) {
+ // I'm leaving this in a broken state for the moment; we should
+ // be measuring/scaling with the print device context, not the
+ // screen device context, but this is good enough to allow
+ // printing reftests to work.
+ double pageWidth = 0, pageHeight = 0;
+ mPresContext->GetPrintSettings()->GetEffectivePageSize(&pageWidth,
+ &pageHeight);
+ mPresContext->SetPageSize(
+ nsSize(mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageWidth)),
+ mPresContext->CSSTwipsToAppUnits(NSToIntFloor(pageHeight))));
+ mPresContext->SetIsRootPaginatedDocument(true);
+ mPresContext->SetPageScale(1.0f);
+ }
+#endif
+ } else {
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+ }
+ }
+
+ nsCOMPtr<nsIInterfaceRequestor> requestor(mContainer);
+ if (requestor) {
+ // Set script-context-owner in the document
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = do_GetInterface(requestor);
+
+ if (window) {
+ nsCOMPtr<Document> curDoc = window->GetExtantDoc();
+ if (aForceSetNewDocument || curDoc != mDocument) {
+ rv = window->SetNewDocument(mDocument, aState, false, aActor);
+ if (NS_FAILED(rv)) {
+ Destroy();
+ return rv;
+ }
+ }
+ }
+ }
+
+ if (aDoCreation && mPresContext) {
+ // The ViewManager and Root View was created above (in
+ // MakeWindow())...
+
+ rv = InitPresentationStuff(!makeCX);
+ }
+
+ return rv;
+}
+
+void nsDocumentViewer::SetNavigationTiming(nsDOMNavigationTiming* timing) {
+ NS_ASSERTION(mDocument, "Must have a document to set navigation timing.");
+ if (mDocument) {
+ mDocument->SetNavigationTiming(timing);
+ }
+}
+
+//
+// LoadComplete(aStatus)
+//
+// aStatus - The status returned from loading the document.
+//
+// This method is called by the container when the document has been
+// completely loaded.
+//
+NS_IMETHODIMP
+nsDocumentViewer::LoadComplete(nsresult aStatus) {
+ /* We need to protect ourself against auto-destruction in case the
+ window is closed while processing the OnLoad event. See bug
+ http://bugzilla.mozilla.org/show_bug.cgi?id=78445 for more
+ explanation.
+ */
+ RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
+
+ // Flush out layout so it's up-to-date by the time onload is called.
+ // Note that this could destroy the window, so do this before
+ // checking for our mDocument and its window.
+ if (mPresShell && !mStopped) {
+ // Hold strong ref because this could conceivably run script
+ RefPtr<PresShell> presShell = mPresShell;
+ presShell->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ nsresult rv = NS_OK;
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ // First, get the window from the document...
+ nsCOMPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+
+ mLoaded = true;
+
+ // Now, fire either an OnLoad or OnError event to the document...
+ bool restoring = false;
+ // XXXbz imagelib kills off the document load for a full-page image with
+ // NS_ERROR_PARSED_DATA_CACHED if it's in the cache. So we want to treat
+ // that one as a success code; otherwise whether we fire onload for the image
+ // will depend on whether it's cached!
+ if (window &&
+ (NS_SUCCEEDED(aStatus) || aStatus == NS_ERROR_PARSED_DATA_CACHED)) {
+ // If this code changes, the code in nsDocLoader::DocLoaderIsEmpty
+ // that fires load events for document.open() cases might need to
+ // be updated too.
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, eLoad);
+ event.mFlags.mBubbles = false;
+ event.mFlags.mCancelable = false;
+ // XXX Dispatching to |window|, but using |document| as the target.
+ event.mTarget = mDocument;
+
+ // If the document presentation is being restored, we don't want to fire
+ // onload to the document content since that would likely confuse scripts
+ // on the page.
+
+ RefPtr<nsDocShell> docShell = nsDocShell::Cast(window->GetDocShell());
+ NS_ENSURE_TRUE(docShell, NS_ERROR_UNEXPECTED);
+
+ // Unfortunately, docShell->GetRestoringDocument() might no longer be set
+ // correctly. In particular, it can be false by now if someone took it upon
+ // themselves to block onload from inside restoration and unblock it later.
+ // But we can detect the restoring case very simply: by whether our
+ // document's readyState is COMPLETE.
+ restoring =
+ (mDocument->GetReadyStateEnum() == Document::READYSTATE_COMPLETE);
+ if (!restoring) {
+ NS_ASSERTION(
+ mDocument->GetReadyStateEnum() == Document::READYSTATE_INTERACTIVE ||
+ // test_stricttransportsecurity.html has old-style
+ // docshell-generated about:blank docs reach this code!
+ (mDocument->GetReadyStateEnum() ==
+ Document::READYSTATE_UNINITIALIZED &&
+ NS_IsAboutBlank(mDocument->GetDocumentURI())),
+ "Bad readystate");
+#ifdef DEBUG
+ bool docShellThinksWeAreRestoring;
+ docShell->GetRestoringDocument(&docShellThinksWeAreRestoring);
+ MOZ_ASSERT(!docShellThinksWeAreRestoring,
+ "How can docshell think we are restoring if we don't have a "
+ "READYSTATE_COMPLETE document?");
+#endif // DEBUG
+ nsCOMPtr<Document> d = mDocument;
+ mDocument->SetReadyStateInternal(Document::READYSTATE_COMPLETE);
+
+ RefPtr<nsDOMNavigationTiming> timing(d->GetNavigationTiming());
+ if (timing) {
+ timing->NotifyLoadEventStart();
+ }
+
+ // Dispatch observer notification to notify observers document load is
+ // complete.
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ nsIPrincipal* principal = d->NodePrincipal();
+ os->NotifyObservers(ToSupports(d),
+ principal->IsSystemPrincipal()
+ ? "chrome-document-loaded"
+ : "content-document-loaded",
+ nullptr);
+ }
+
+ nsPIDOMWindowInner* innerWindow = window->GetCurrentInnerWindow();
+ d->SetLoadEventFiring(true);
+ RefPtr<nsPresContext> presContext = mPresContext;
+ // MOZ_KnownLive due to bug 1506441
+ EventDispatcher::Dispatch(
+ MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)), presContext, &event,
+ nullptr, &status);
+ d->SetLoadEventFiring(false);
+
+ if (timing) {
+ timing->NotifyLoadEventEnd();
+ }
+
+ if (innerWindow) {
+ innerWindow->QueuePerformanceNavigationTiming();
+ }
+ }
+ } else {
+ // XXX: Should fire error event to the document...
+
+ // If our load was explicitly aborted, then we want to set our
+ // readyState to COMPLETE, and fire a readystatechange event.
+ if (aStatus == NS_BINDING_ABORTED && mDocument) {
+ mDocument->NotifyAbortedLoad();
+ }
+ }
+
+ // Notify the document that it has been shown (regardless of whether
+ // it was just loaded). Note: mDocument may be null now if the above
+ // firing of onload caused the document to unload. Or, mDocument may not be
+ // the "current active" document, if the above firing of onload caused our
+ // docshell to navigate away. NOTE: In this latter scenario, it's likely that
+ // we fired pagehide (when navigating away) without ever having fired
+ // pageshow, and that's pretty broken... Fortunately, this should be rare.
+ // (It requires us to spin the event loop in onload handler, e.g. via sync
+ // XHR, in order for the navigation-away to happen before onload completes.)
+ // We skip firing pageshow if we're currently handling unload, or if loading
+ // was explicitly aborted.
+ if (mDocument && mDocument->IsCurrentActiveDocument() &&
+ aStatus != NS_BINDING_ABORTED) {
+ // Re-get window, since it might have changed during above firing of onload
+ window = mDocument->GetWindow();
+ if (window) {
+ nsIDocShell* docShell = window->GetDocShell();
+ bool isInUnload;
+ if (docShell && NS_SUCCEEDED(docShell->GetIsInUnload(&isInUnload)) &&
+ !isInUnload) {
+ mDocument->OnPageShow(restoring, nullptr);
+ }
+ }
+ }
+
+ if (!mStopped) {
+ if (mDocument) {
+ nsCOMPtr<Document> document = mDocument;
+ document->ScrollToRef();
+ }
+
+ // Now that the document has loaded, we can tell the presshell
+ // to unsuppress painting.
+ if (mPresShell) {
+ RefPtr<PresShell> presShell = mPresShell;
+ presShell->UnsuppressPainting();
+ // mPresShell could have been removed now, see bug 378682/421432
+ if (mPresShell) {
+ mPresShell->LoadComplete();
+ }
+ }
+ }
+
+ if (mDocument && !restoring) {
+ mDocument->LoadEventFired();
+ }
+
+ // It's probably a good idea to GC soon since we have finished loading.
+ nsJSContext::PokeGC(
+ JS::GCReason::LOAD_END,
+ mDocument ? mDocument->GetWrapperPreserveColor() : nullptr);
+
+#ifdef NS_PRINTING
+ // Check to see if someone tried to print during the load
+ if (window) {
+ auto* outerWin = nsGlobalWindowOuter::Cast(window);
+ outerWin->StopDelayingPrintingUntilAfterLoad();
+ if (outerWin->DelayedPrintUntilAfterLoad()) {
+ // We call into the inner because it ensures there's an active document
+ // and such, and it also waits until the whole thing completes, which is
+ // nice because it allows us to close if needed right here.
+ if (RefPtr inner =
+ nsGlobalWindowInner::Cast(window->GetCurrentInnerWindow())) {
+ inner->Print(IgnoreErrors());
+ }
+ if (outerWin->DelayedCloseForPrinting()) {
+ outerWin->Close();
+ }
+ } else {
+ MOZ_ASSERT(!outerWin->DelayedCloseForPrinting());
+ }
+ }
+#endif
+
+ return rv;
+}
+
+bool nsDocumentViewer::GetLoadCompleted() { return mLoaded; }
+
+bool nsDocumentViewer::GetIsStopped() { return mStopped; }
+
+NS_IMETHODIMP
+nsDocumentViewer::PermitUnload(PermitUnloadAction aAction,
+ bool* aPermitUnload) {
+ // We're going to be running JS and nested event loops, which could cause our
+ // DocShell to be destroyed. Make sure we stay alive until the end of the
+ // function.
+ RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
+
+ if (StaticPrefs::dom_disable_beforeunload()) {
+ aAction = eDontPromptAndUnload;
+ }
+
+ *aPermitUnload = true;
+
+ RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext();
+ if (!bc) {
+ return NS_OK;
+ }
+
+ // Per spec, we need to increase the ignore-opens-during-unload counter while
+ // dispatching the "beforeunload" event on both the document we're currently
+ // dispatching the event to and the document that we explicitly asked to
+ // unload.
+ IgnoreOpensDuringUnload ignoreOpens(mDocument);
+
+ bool foundBlocker = false;
+ bool foundOOPListener = false;
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (!aBC->IsInProcess()) {
+ WindowContext* wc = aBC->GetCurrentWindowContext();
+ if (wc && wc->HasBeforeUnload()) {
+ foundOOPListener = true;
+ }
+ } else if (aBC->GetDocShell()) {
+ nsCOMPtr<nsIDocumentViewer> viewer(aBC->GetDocShell()->GetDocViewer());
+ if (viewer && viewer->DispatchBeforeUnload() == eRequestBlockNavigation) {
+ foundBlocker = true;
+ }
+ }
+ });
+
+ if (!foundOOPListener) {
+ if (!foundBlocker) {
+ return NS_OK;
+ }
+ if (aAction != ePrompt) {
+ *aPermitUnload = aAction == eDontPromptAndUnload;
+ return NS_OK;
+ }
+ }
+
+ // NB: we nullcheck mDocument because it might now be dead as a result of
+ // the event being dispatched.
+ RefPtr<WindowGlobalChild> wgc(mDocument ? mDocument->GetWindowGlobalChild()
+ : nullptr);
+ if (!wgc) {
+ return NS_OK;
+ }
+
+ nsAutoSyncOperation sync(mDocument, SyncOperationBehavior::eSuspendInput);
+ AutoSuppressEventHandlingAndSuspend seh(bc->Group());
+
+ mInPermitUnloadPrompt = true;
+
+ bool done = false;
+ wgc->SendCheckPermitUnload(
+ foundBlocker, aAction,
+ [&](bool aPermit) {
+ done = true;
+ *aPermitUnload = aPermit;
+ },
+ [&](auto) {
+ // If the prompt aborted, we tell our consumer that it is not allowed
+ // to unload the page. One reason that prompts abort is that the user
+ // performed some action that caused the page to unload while our prompt
+ // was active. In those cases we don't want our consumer to also unload
+ // the page.
+ //
+ // XXX: Are there other cases where prompts can abort? Is it ok to
+ // prevent unloading the page in those cases?
+ done = true;
+ *aPermitUnload = false;
+ });
+
+ SpinEventLoopUntil("nsDocumentViewer::PermitUnload"_ns,
+ [&]() { return done; });
+
+ mInPermitUnloadPrompt = false;
+ return NS_OK;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY PermitUnloadResult
+nsDocumentViewer::DispatchBeforeUnload() {
+ AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
+
+ if (!mDocument || mInPermitUnload || mInPermitUnloadPrompt) {
+ return eAllowNavigation;
+ }
+
+ // First, get the script global object from the document...
+ RefPtr<nsGlobalWindowOuter> window =
+ nsGlobalWindowOuter::Cast(mDocument->GetWindow());
+ if (!window) {
+ // This is odd, but not fatal
+ NS_WARNING("window not set for document!");
+ return eAllowNavigation;
+ }
+
+ NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), "This is unsafe");
+
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#prompt-to-unload-a-document
+ // Create an RAII object on mDocument that will increment the
+ // should-ignore-opens-during-unload counter on initialization
+ // and decrement it again when it goes out of score (regardless
+ // of how we exit this function).
+ IgnoreOpensDuringUnload ignoreOpens(mDocument);
+
+ // Now, fire an BeforeUnload event to the document and see if it's ok
+ // to unload...
+ nsPresContext* presContext = mDocument->GetPresContext();
+ RefPtr<BeforeUnloadEvent> event =
+ new BeforeUnloadEvent(mDocument, presContext, nullptr);
+ event->InitEvent(u"beforeunload"_ns, false, true);
+
+ // Dispatching to |window|, but using |document| as the target.
+ event->SetTarget(mDocument);
+ event->SetTrusted(true);
+
+ // In evil cases we might be destroyed while handling the
+ // onbeforeunload event, don't let that happen. (see also bug#331040)
+ RefPtr<nsDocumentViewer> kungFuDeathGrip(this);
+
+ {
+ // Never permit popups from the beforeunload handler, no matter
+ // how we get here.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext();
+ NS_ASSERTION(bc, "should have a browsing context in document viewer");
+
+ // Never permit dialogs from the beforeunload handler
+ nsGlobalWindowOuter::TemporarilyDisableDialogs disableDialogs(bc);
+
+ Document::PageUnloadingEventTimeStamp timestamp(mDocument);
+
+ mInPermitUnload = true;
+ RefPtr<nsPresContext> presContext = mPresContext;
+ EventDispatcher::DispatchDOMEvent(window, nullptr, event, presContext,
+ nullptr);
+ mInPermitUnload = false;
+ }
+
+ nsAutoString text;
+ event->GetReturnValue(text);
+
+ // NB: we nullcheck mDocument because it might now be dead as a result of
+ // the event being dispatched.
+ if (window->AreDialogsEnabled() && mDocument &&
+ !(mDocument->GetSandboxFlags() & SANDBOXED_MODALS) &&
+ (!StaticPrefs::dom_require_user_interaction_for_beforeunload() ||
+ mDocument->UserHasInteracted()) &&
+ (event->WidgetEventPtr()->DefaultPrevented() || !text.IsEmpty())) {
+ return eRequestBlockNavigation;
+ }
+ return eAllowNavigation;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetBeforeUnloadFiring(bool* aInEvent) {
+ *aInEvent = mInPermitUnload;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetInPermitUnload(bool* aInEvent) {
+ *aInEvent = mInPermitUnloadPrompt;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PageHide(bool aIsUnload) {
+ AutoDontWarnAboutSyncXHR disableSyncXHRWarning;
+
+ mHidden = true;
+
+ if (!mDocument) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (aIsUnload) {
+ // Poke the GC. The window might be collectable garbage now.
+ nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE,
+ mDocument->GetWrapperPreserveColor(),
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay() * 2));
+ }
+
+ mDocument->OnPageHide(!aIsUnload, nullptr);
+
+ // inform the window so that the focus state is reset.
+ NS_ENSURE_STATE(mDocument);
+ nsPIDOMWindowOuter* window = mDocument->GetWindow();
+ if (window) window->PageHidden();
+
+ if (aIsUnload) {
+ // if Destroy() was called during OnPageHide(), mDocument is nullptr.
+ NS_ENSURE_STATE(mDocument);
+
+ // First, get the window from the document...
+ RefPtr<nsPIDOMWindowOuter> window = mDocument->GetWindow();
+
+ if (!window) {
+ // Fail if no window is available...
+ NS_WARNING("window not set for document!");
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ // https://html.spec.whatwg.org/multipage/browsing-the-web.html#unload-a-document
+ // Create an RAII object on mDocument that will increment the
+ // should-ignore-opens-during-unload counter on initialization
+ // and decrement it again when it goes out of scope.
+ IgnoreOpensDuringUnload ignoreOpens(mDocument);
+
+ // Now, fire an Unload event to the document...
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetEvent event(true, eUnload);
+ event.mFlags.mBubbles = false;
+ // XXX Dispatching to |window|, but using |document| as the target.
+ event.mTarget = mDocument;
+
+ // Never permit popups from the unload handler, no matter how we get
+ // here.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ Document::PageUnloadingEventTimeStamp timestamp(mDocument);
+
+ RefPtr<nsPresContext> presContext = mPresContext;
+ // MOZ_KnownLive due to bug 1506441
+ EventDispatcher::Dispatch(MOZ_KnownLive(nsGlobalWindowOuter::Cast(window)),
+ presContext, &event, nullptr, &status);
+ }
+
+ // look for open menupopups and close them after the unload event, in case
+ // the unload event listeners open any new popups
+ nsContentUtils::HidePopupsInDocument(mDocument);
+
+ return NS_OK;
+}
+
+static void AttachContainerRecurse(nsIDocShell* aShell) {
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ aShell->GetDocViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ viewer->SetIsHidden(false);
+ Document* doc = viewer->GetDocument();
+ if (doc) {
+ doc->SetContainer(static_cast<nsDocShell*>(aShell));
+ }
+ if (PresShell* presShell = viewer->GetPresShell()) {
+ presShell->SetForwardingContainer(WeakPtr<nsDocShell>());
+ }
+ }
+
+ // Now recurse through the children
+ int32_t childCount;
+ aShell->GetInProcessChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ aShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem);
+ AttachContainerRecurse(shell);
+ }
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Open(nsISupports* aState, nsISHEntry* aSHEntry) {
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+
+ if (mDocument) {
+ mDocument->SetContainer(mContainer);
+ }
+
+ nsresult rv = InitInternal(mParentWidget, aState, nullptr, mBounds, false);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mHidden = false;
+
+ if (mPresShell) mPresShell->SetForwardingContainer(WeakPtr<nsDocShell>());
+
+ // Rehook the child presentations. The child shells are still in
+ // session history, so get them from there.
+
+ if (aSHEntry) {
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ int32_t itemIndex = 0;
+ while (NS_SUCCEEDED(
+ aSHEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) &&
+ item) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
+ AttachContainerRecurse(shell);
+ }
+ }
+
+ SyncParentSubDocMap();
+
+ ReinitializeFocusListener();
+
+ // XXX re-enable image animations once that works correctly
+
+ PrepareToStartLoad();
+
+ // When loading a page from the bfcache with puppet widgets, we do the
+ // widget attachment here (it is otherwise done in MakeWindow, which is
+ // called for non-bfcache pages in the history, but not bfcache pages).
+ // Attachment is necessary, since we get detached when another page
+ // is browsed to. That is, if we are one page A, then when we go to
+ // page B, we detach. So page A's view has no widget. If we then go
+ // back to it, and it is in the bfcache, we will use that view, which
+ // doesn't have a widget. The attach call here will properly attach us.
+ if (nsIWidget::UsePuppetWidgets() && mPresContext &&
+ ShouldAttachToTopLevel()) {
+ // If the old view is already attached to our parent, detach
+ DetachFromTopLevelWidget();
+
+ nsViewManager* vm = GetViewManager();
+ MOZ_ASSERT(vm, "no view manager");
+ nsView* v = vm->GetRootView();
+ MOZ_ASSERT(v, "no root view");
+ MOZ_ASSERT(mParentWidget, "no mParentWidget to set");
+ v->AttachToTopLevelWidget(mParentWidget);
+
+ mAttachedToParent = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Close(nsISHEntry* aSHEntry) {
+ // All callers are supposed to call close to break circular
+ // references. If we do this stuff in the destructor, the
+ // destructor might never be called (especially if we're being
+ // used from JS.
+
+ mSHEntry = aSHEntry;
+
+ // Close is also needed to disable scripts during paint suppression,
+ // since we transfer the existing global object to the new document
+ // that is loaded. In the future, the global object may become a proxy
+ // for an object that can be switched in and out so that we don't need
+ // to disable scripts during paint suppression.
+
+ if (!mDocument) return NS_OK;
+
+ if (mSHEntry) {
+ if (mBFCachePreventionObserver) {
+ mBFCachePreventionObserver->Disconnect();
+ }
+ mBFCachePreventionObserver = new BFCachePreventionObserver(mDocument);
+ mDocument->AddMutationObserver(mBFCachePreventionObserver);
+ }
+
+#ifdef NS_PRINTING
+ // A Close was called while we were printing
+ // so don't clear the ScriptGlobalObject
+ // or clear the mDocument below
+ if (mPrintJob && !mClosingWhilePrinting) {
+ mClosingWhilePrinting = true;
+ } else
+#endif
+ {
+ // out of band cleanup of docshell
+ mDocument->SetScriptGlobalObject(nullptr);
+
+ if (!mSHEntry && mDocument) mDocument->RemovedFromDocShell();
+ }
+
+ RemoveFocusListener();
+ return NS_OK;
+}
+
+static void DetachContainerRecurse(nsIDocShell* aShell) {
+ // Unhook this docshell's presentation
+ aShell->SynchronizeLayoutHistoryState();
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ aShell->GetDocViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ if (Document* doc = viewer->GetDocument()) {
+ doc->SetContainer(nullptr);
+ }
+ if (PresShell* presShell = viewer->GetPresShell()) {
+ auto weakShell = static_cast<nsDocShell*>(aShell);
+ presShell->SetForwardingContainer(weakShell);
+ }
+ }
+
+ // Now recurse through the children
+ int32_t childCount;
+ aShell->GetInProcessChildCount(&childCount);
+ for (int32_t i = 0; i < childCount; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> childItem;
+ aShell->GetInProcessChildAt(i, getter_AddRefs(childItem));
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(childItem);
+ DetachContainerRecurse(shell);
+ }
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Destroy() {
+ // Don't let the document get unloaded while we are printing.
+ // this could happen if we hit the back button during printing.
+ // We also keep the viewer from being cached in session history, since
+ // we require all documents there to be sanitized.
+ if (mDestroyBlockedCount != 0) {
+ return NS_OK;
+ }
+
+#ifdef NS_PRINTING
+ // Here is where we check to see if the document was still being prepared
+ // for printing when it was asked to be destroy from someone externally
+ // This usually happens if the document is unloaded while the user is in the
+ // Print Dialog
+ //
+ // So we flip the bool to remember that the document is going away
+ // and we can clean up and abort later after returning from the Print Dialog
+ if (mPrintJob && mPrintJob->CheckBeforeDestroy()) {
+ return NS_OK;
+ }
+#endif
+
+ // We want to make sure to disconnect mBFCachePreventionObserver before we
+ // Sanitize() below.
+ if (mBFCachePreventionObserver) {
+ mBFCachePreventionObserver->Disconnect();
+ mBFCachePreventionObserver = nullptr;
+ }
+
+ if (mSHEntry && mDocument && !mDocument->IsBFCachingAllowed()) {
+ // Just drop the SHEntry now and pretend like we never even tried to bfcache
+ // this viewer. This should only happen when someone calls
+ // DisallowBFCaching() after CanSavePresentation() already ran. Ensure that
+ // the SHEntry has no viewer and its state is synced up. We want to do this
+ // via a stack reference, in case those calls mess with our members.
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("BFCache not allowed, dropping SHEntry"));
+ nsCOMPtr<nsISHEntry> shEntry = std::move(mSHEntry);
+ shEntry->SetDocumentViewer(nullptr);
+ shEntry->SyncPresentationState();
+ }
+
+ // If we were told to put ourselves into session history instead of destroy
+ // the presentation, do that now.
+ if (mSHEntry) {
+ if (mPresShell) mPresShell->Freeze();
+
+ // Make sure the presentation isn't torn down by Hide().
+ mSHEntry->SetSticky(mIsSticky);
+ mIsSticky = true;
+
+ // Remove our root view from the view hierarchy.
+ if (mPresShell) {
+ nsViewManager* vm = mPresShell->GetViewManager();
+ if (vm) {
+ nsView* rootView = vm->GetRootView();
+
+ if (rootView) {
+ nsView* rootViewParent = rootView->GetParent();
+ if (rootViewParent) {
+ nsView* subdocview = rootViewParent->GetParent();
+ if (subdocview) {
+ nsIFrame* f = subdocview->GetFrame();
+ if (f) {
+ nsSubDocumentFrame* s = do_QueryFrame(f);
+ if (s) {
+ s->ClearDisplayItems();
+ }
+ }
+ }
+ nsViewManager* parentVM = rootViewParent->GetViewManager();
+ if (parentVM) {
+ parentVM->RemoveChild(rootView);
+ }
+ }
+ }
+ }
+ }
+
+ Hide();
+
+ // This is after Hide() so that the user doesn't see the inputs clear.
+ if (mDocument) {
+ mDocument->Sanitize();
+ }
+
+ // Reverse ownership. Do this *after* calling sanitize so that sanitize
+ // doesn't cause mutations that make the SHEntry drop the presentation
+
+ // Grab a reference to mSHEntry before calling into things like
+ // SyncPresentationState that might mess with our members.
+ nsCOMPtr<nsISHEntry> shEntry =
+ std::move(mSHEntry); // we'll need this below
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("Storing content viewer into cache entry"));
+ shEntry->SetDocumentViewer(this);
+
+ // Always sync the presentation state. That way even if someone screws up
+ // and shEntry has no window state at this point we'll be ok; we just won't
+ // cache ourselves.
+ shEntry->SyncPresentationState();
+ // XXX Synchronize layout history state to parent once bfcache is supported
+ // in session-history-in-parent.
+
+ // Shut down accessibility for the document before we start to tear it down.
+#ifdef ACCESSIBILITY
+ if (mPresShell) {
+ a11y::DocAccessible* docAcc = mPresShell->GetDocAccessible();
+ if (docAcc) {
+ docAcc->Shutdown();
+ }
+ }
+#endif
+
+ // Break the link from the document/presentation to the docshell, so that
+ // link traversals cannot affect the currently-loaded document.
+ // When the presentation is restored, Open() and InitInternal() will reset
+ // these pointers to their original values.
+
+ if (mDocument) {
+ mDocument->SetContainer(nullptr);
+ }
+ if (mPresShell) {
+ mPresShell->SetForwardingContainer(mContainer);
+ }
+
+ // Do the same for our children. Note that we need to get the child
+ // docshells from the SHEntry now; the docshell will have cleared them.
+ nsCOMPtr<nsIDocShellTreeItem> item;
+ int32_t itemIndex = 0;
+ while (NS_SUCCEEDED(
+ shEntry->ChildShellAt(itemIndex++, getter_AddRefs(item))) &&
+ item) {
+ nsCOMPtr<nsIDocShell> shell = do_QueryInterface(item);
+ DetachContainerRecurse(shell);
+ }
+
+ return NS_OK;
+ }
+
+ // The document was not put in the bfcache
+
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+ if (mDocument) {
+ mDocument->Destroy();
+ mDocument = nullptr;
+ }
+
+ // All callers are supposed to call destroy to break circular
+ // references. If we do this stuff in the destructor, the
+ // destructor might never be called (especially if we're being
+ // used from JS.
+
+#ifdef NS_PRINTING
+ if (mPrintJob) {
+ RefPtr<nsPrintJob> printJob = std::move(mPrintJob);
+# ifdef NS_PRINT_PREVIEW
+ if (printJob->CreatedForPrintPreview()) {
+ printJob->FinishPrintPreview();
+ }
+# endif
+ printJob->Destroy();
+ MOZ_ASSERT(!mPrintJob,
+ "mPrintJob shouldn't be recreated while destroying it");
+ }
+#endif
+
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+
+ mDeviceContext = nullptr;
+
+ if (mPresContext) {
+ DestroyPresContext();
+ }
+
+ mWindow = nullptr;
+ mViewManager = nullptr;
+ mContainer = WeakPtr<nsDocShell>();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Stop(void) {
+ NS_ASSERTION(mDocument, "Stop called too early or too late");
+ if (mDocument) {
+ mDocument->StopDocumentLoad();
+ }
+
+ mStopped = true;
+
+ if (!mLoaded && mPresShell) {
+ // Well, we might as well paint what we have so far.
+ RefPtr<PresShell> presShell = mPresShell; // bug 378682
+ presShell->UnsuppressPainting();
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetDOMDocument(Document** aResult) {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ nsCOMPtr<Document> document = mDocument;
+ document.forget(aResult);
+ return NS_OK;
+}
+
+Document* nsDocumentViewer::GetDocument() { return mDocument; }
+
+nsresult nsDocumentViewer::SetDocument(Document* aDocument) {
+ // Assumptions:
+ //
+ // 1) this document viewer has been initialized with a call to Init().
+ // 2) the stylesheets associated with the document have been added
+ // to the document.
+
+ // XXX Right now, this method assumes that the layout of the current
+ // document hasn't started yet. More cleanup will probably be
+ // necessary to make this method work for the case when layout *has*
+ // occurred for the current document.
+ // That work can happen when and if it is needed.
+
+ if (!aDocument) return NS_ERROR_NULL_POINTER;
+
+ return SetDocumentInternal(aDocument, false);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetDocumentInternal(Document* aDocument,
+ bool aForceReuseInnerWindow) {
+ MOZ_ASSERT(aDocument);
+
+ // Set new container
+ aDocument->SetContainer(mContainer);
+
+ if (mDocument != aDocument) {
+ if (aForceReuseInnerWindow) {
+ // Transfer the navigation timing information to the new document, since
+ // we're keeping the same inner and hence should really have the same
+ // timing information.
+ aDocument->SetNavigationTiming(mDocument->GetNavigationTiming());
+ }
+
+ if (mDocument &&
+ (mDocument->IsStaticDocument() || aDocument->IsStaticDocument())) {
+ nsContentUtils::AddScriptRunner(NewRunnableMethod(
+ "Document::Destroy", mDocument, &Document::Destroy));
+ }
+
+ // Clear the list of old child docshells. Child docshells for the new
+ // document will be constructed as frames are created.
+ if (!aDocument->IsStaticDocument()) {
+ nsCOMPtr<nsIDocShell> node(mContainer);
+ if (node) {
+ int32_t count;
+ node->GetInProcessChildCount(&count);
+ for (int32_t i = 0; i < count; ++i) {
+ nsCOMPtr<nsIDocShellTreeItem> child;
+ node->GetInProcessChildAt(0, getter_AddRefs(child));
+ node->RemoveChild(child);
+ }
+ }
+ }
+
+ // Replace the old document with the new one. Do this only when
+ // the new document really is a new document.
+ mDocument = aDocument;
+
+ // Set the script global object on the new document
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ mContainer ? mContainer->GetWindow() : nullptr;
+ if (window) {
+ nsresult rv =
+ window->SetNewDocument(aDocument, nullptr, aForceReuseInnerWindow);
+ if (NS_FAILED(rv)) {
+ Destroy();
+ return rv;
+ }
+ }
+ }
+
+ nsresult rv = SyncParentSubDocMap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Replace the current pres shell with a new shell for the new document
+
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ if (mPresContext) {
+ DestroyPresContext();
+
+ mWindow = nullptr;
+ rv = InitInternal(mParentWidget, nullptr, nullptr, mBounds, true, true,
+ false);
+ }
+
+ return rv;
+}
+
+PresShell* nsDocumentViewer::GetPresShell() { return mPresShell; }
+
+nsPresContext* nsDocumentViewer::GetPresContext() { return mPresContext; }
+
+nsViewManager* nsDocumentViewer::GetViewManager() { return mViewManager; }
+
+NS_IMETHODIMP
+nsDocumentViewer::GetBounds(nsIntRect& aResult) {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ aResult = mBounds;
+ return NS_OK;
+}
+
+nsIDocumentViewer* nsDocumentViewer::GetPreviousViewer() {
+ return mPreviousViewer;
+}
+
+void nsDocumentViewer::SetPreviousViewer(nsIDocumentViewer* aViewer) {
+ // NOTE: |Show| sets |mPreviousViewer| to null without calling this
+ // function.
+
+ if (aViewer) {
+ NS_ASSERTION(!mPreviousViewer,
+ "can't set previous viewer when there already is one");
+
+ // In a multiple chaining situation (which occurs when running a thrashing
+ // test like i-bench or jrgm's tests with no delay), we can build up a
+ // whole chain of viewers. In order to avoid this, we always set our
+ // previous viewer to the MOST previous viewer in the chain, and then dump
+ // the intermediate link from the chain. This ensures that at most only 2
+ // documents are alive and undestroyed at any given time (the one that is
+ // showing and the one that is loading with painting suppressed). It's very
+ // important that if this ever gets changed the code before the
+ // RestorePresentation call in nsDocShell::InternalLoad be changed
+ // accordingly.
+ //
+ // Make sure we hold a strong ref to prevViewer here, since we'll
+ // tell aViewer to drop it.
+ nsCOMPtr<nsIDocumentViewer> prevViewer = aViewer->GetPreviousViewer();
+ if (prevViewer) {
+ aViewer->SetPreviousViewer(nullptr);
+ aViewer->Destroy();
+ return SetPreviousViewer(prevViewer);
+ }
+ }
+
+ mPreviousViewer = aViewer;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetBoundsWithFlags(const nsIntRect& aBounds,
+ uint32_t aFlags) {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ bool boundsChanged = !mBounds.IsEqualEdges(aBounds);
+ mBounds = aBounds;
+
+ if (mWindow && !mAttachedToParent) {
+ // Resize the widget, but don't trigger repaint. Layout will generate
+ // repaint requests during reflow.
+ mWindow->Resize(aBounds.x, aBounds.y, aBounds.width, aBounds.height, false);
+ } else if (mPresContext && mViewManager) {
+ // Ensure presContext's deviceContext is up to date, as we sometimes get
+ // here before a resolution-change notification has been fully handled
+ // during display configuration changes, especially when there are lots
+ // of windows/widgets competing to handle the notifications.
+ // (See bug 1154125.)
+ if (mPresContext->DeviceContext()->CheckDPIChange()) {
+ mPresContext->UIResolutionChangedSync();
+ }
+
+ int32_t p2a = mPresContext->AppUnitsPerDevPixel();
+ nscoord width = NSIntPixelsToAppUnits(mBounds.width, p2a);
+ nscoord height = NSIntPixelsToAppUnits(mBounds.height, p2a);
+ nsView* rootView = mViewManager->GetRootView();
+ if (boundsChanged && rootView) {
+ nsRect viewDims = rootView->GetDimensions();
+ // If the view/frame tree and prescontext visible area already has the new
+ // size but we did not, then it's likely that we got reflowed in response
+ // to a call to GetContentSize. Thus there is a disconnect between the
+ // size on the document viewer/docshell/containing widget and view
+ // tree/frame tree/prescontext visible area). SetWindowDimensions compares
+ // to the root view dimenstions to determine if it needs to do anything;
+ // if they are the same as the new size it won't do anything, but we still
+ // need to invalidate because what we want to draw to the screen has
+ // changed.
+ if (viewDims.width == width && viewDims.height == height) {
+ nsIFrame* f = rootView->GetFrame();
+ if (f) {
+ f->InvalidateFrame();
+ }
+ }
+ }
+
+ mViewManager->SetWindowDimensions(
+ width, height, !!(aFlags & nsIDocumentViewer::eDelayResize));
+ }
+
+ // If there's a previous viewer, it's the one that's actually showing,
+ // so be sure to resize it as well so it paints over the right area.
+ // This may slow down the performance of the new page load, but resize
+ // during load is also probably a relatively unusual condition
+ // relating to things being hidden while something is loaded. It so
+ // happens that Firefox does this a good bit with its infobar, and it
+ // looks ugly if we don't do this.
+ if (mPreviousViewer) {
+ nsCOMPtr<nsIDocumentViewer> previousViewer = mPreviousViewer;
+ previousViewer->SetBounds(aBounds);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetBounds(const nsIntRect& aBounds) {
+ return SetBoundsWithFlags(aBounds, 0);
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Move(int32_t aX, int32_t aY) {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+ mBounds.MoveTo(aX, aY);
+ if (mWindow) {
+ mWindow->Move(aX, aY);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Show() {
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_AVAILABLE);
+
+ // We don't need the previous viewer anymore since we're not
+ // displaying it.
+ if (mPreviousViewer) {
+ // This little dance *may* only be to keep
+ // PresShell::EndObservingDocument happy, but I'm not sure.
+ nsCOMPtr<nsIDocumentViewer> prevViewer(mPreviousViewer);
+ mPreviousViewer = nullptr;
+ prevViewer->Destroy();
+
+ // Make sure we don't have too many cached ContentViewers
+ nsCOMPtr<nsIDocShellTreeItem> treeItem(mContainer);
+ if (treeItem) {
+ // We need to find the root DocShell since only that object has an
+ // SHistory and we need the SHistory to evict content viewers
+ nsCOMPtr<nsIDocShellTreeItem> root;
+ treeItem->GetInProcessSameTypeRootTreeItem(getter_AddRefs(root));
+ nsCOMPtr<nsIWebNavigation> webNav = do_QueryInterface(root);
+ RefPtr<ChildSHistory> history = webNav->GetSessionHistory();
+ if (!mozilla::SessionHistoryInParent() && history) {
+ int32_t prevIndex, loadedIndex;
+ nsCOMPtr<nsIDocShell> docShell = do_QueryInterface(treeItem);
+ docShell->GetPreviousEntryIndex(&prevIndex);
+ docShell->GetLoadedEntryIndex(&loadedIndex);
+ MOZ_LOG(gPageCacheLog, LogLevel::Verbose,
+ ("About to evict content viewers: prev=%d, loaded=%d",
+ prevIndex, loadedIndex));
+ history->LegacySHistory()->EvictOutOfRangeDocumentViewers(loadedIndex);
+ }
+ }
+ }
+
+ if (mWindow) {
+ // When attached to a top level xul window, we do not need to call
+ // Show on the widget. Underlying window management code handles
+ // this when the window is initialized.
+ if (!mAttachedToParent) {
+ mWindow->Show(true);
+ }
+ }
+
+ // Hold on to the document so we can use it after the script blocker below
+ // has been released (which might re-entrantly call into other
+ // nsDocumentViewer methods).
+ nsCOMPtr<Document> document = mDocument;
+
+ if (mDocument && !mPresShell) {
+ // The InitPresentationStuff call below requires a script blocker, because
+ // its PresShell::Initialize call can cause scripts to run and therefore
+ // re-entrant calls to nsDocumentViewer methods to be made.
+ nsAutoScriptBlocker scriptBlocker;
+
+ NS_ASSERTION(!mWindow, "Window already created but no presshell?");
+
+ nsCOMPtr<nsIBaseWindow> base_win(mContainer);
+ if (base_win) {
+ base_win->GetParentWidget(&mParentWidget);
+ if (mParentWidget) {
+ // GetParentWidget AddRefs, but mParentWidget is weak
+ mParentWidget->Release();
+ }
+ }
+
+ nsView* containerView = FindContainerView();
+
+ nsresult rv = CreateDeviceContext(containerView);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Create presentation context
+ NS_ASSERTION(!mPresContext,
+ "Shouldn't have a prescontext if we have no shell!");
+ mPresContext = CreatePresContext(mDocument, nsPresContext::eContext_Galley,
+ containerView);
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+
+ rv = mPresContext->Init(mDeviceContext);
+ if (NS_FAILED(rv)) {
+ mPresContext = nullptr;
+ return rv;
+ }
+
+ rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width),
+ mPresContext->DevPixelsToAppUnits(mBounds.height)),
+ containerView);
+ if (NS_FAILED(rv)) return rv;
+
+ if (mPresContext) {
+ Hide();
+
+ rv = InitPresentationStuff(mDocument->MayStartLayout());
+ }
+
+ // If we get here the document load has already started and the
+ // window is shown because some JS on the page caused it to be
+ // shown...
+
+ if (mPresShell) {
+ RefPtr<PresShell> presShell = mPresShell; // bug 378682
+ presShell->UnsuppressPainting();
+ }
+ }
+
+ // Notify observers that a new page has been shown. This will get run
+ // from the event loop after we actually draw the page.
+ RefPtr<nsDocumentShownDispatcher> event =
+ new nsDocumentShownDispatcher(document);
+ document->Dispatch(event.forget());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::Hide() {
+ if (!mAttachedToParent && mWindow) {
+ mWindow->Show(false);
+ }
+
+ if (!mPresShell) return NS_OK;
+
+ NS_ASSERTION(mPresContext, "Can't have a presshell and no prescontext!");
+
+ // Avoid leaking the old viewer.
+ if (mPreviousViewer) {
+ mPreviousViewer->Destroy();
+ mPreviousViewer = nullptr;
+ }
+
+ if (mIsSticky) {
+ // This window is sticky, that means that it might be shown again
+ // and we don't want the presshell n' all that to be thrown away
+ // just because the window is hidden.
+
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (docShell) {
+#ifdef DEBUG
+ nsCOMPtr<nsIDocumentViewer> currentViewer;
+ docShell->GetDocViewer(getter_AddRefs(currentViewer));
+ MOZ_ASSERT(currentViewer == this);
+#endif
+ nsCOMPtr<nsILayoutHistoryState> layoutState;
+ mPresShell->CaptureHistoryState(getter_AddRefs(layoutState));
+ }
+
+ // Do not run ScriptRunners queued by DestroyPresShell() in the intermediate
+ // state before we're done destroying PresShell, PresContext, ViewManager,
+ // etc.
+ nsAutoScriptBlocker scriptBlocker;
+
+ DestroyPresShell();
+
+ DestroyPresContext();
+
+ mViewManager = nullptr;
+ mWindow = nullptr;
+ mDeviceContext = nullptr;
+ mParentWidget = nullptr;
+
+ nsCOMPtr<nsIBaseWindow> base_win(mContainer);
+
+ if (base_win && !mAttachedToParent) {
+ base_win->SetParentWidget(nullptr);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetSticky(bool* aSticky) {
+ *aSticky = mIsSticky;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetSticky(bool aSticky) {
+ mIsSticky = aSticky;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ClearHistoryEntry() {
+ if (mDocument) {
+ nsJSContext::PokeGC(JS::GCReason::PAGE_HIDE,
+ mDocument->GetWrapperPreserveColor(),
+ TimeDuration::FromMilliseconds(
+ StaticPrefs::javascript_options_gc_delay() * 2));
+ }
+
+ mSHEntry = nullptr;
+ return NS_OK;
+}
+
+//-------------------------------------------------------
+
+nsresult nsDocumentViewer::MakeWindow(const nsSize& aSize,
+ nsView* aContainerView) {
+ if (GetIsPrintPreview()) {
+ return NS_OK;
+ }
+
+ bool shouldAttach = ShouldAttachToTopLevel();
+
+ if (shouldAttach) {
+ // If the old view is already attached to our parent, detach
+ DetachFromTopLevelWidget();
+ }
+
+ mViewManager = new nsViewManager();
+
+ nsDeviceContext* dx = mPresContext->DeviceContext();
+
+ nsresult rv = mViewManager->Init(dx);
+ if (NS_FAILED(rv)) return rv;
+
+ // The root view is always at 0,0.
+ nsRect tbounds(nsPoint(0, 0), aSize);
+ // Create a view
+ nsView* view = mViewManager->CreateView(tbounds, aContainerView);
+ if (!view) return NS_ERROR_OUT_OF_MEMORY;
+
+ // Create a widget if we were given a parent widget or don't have a
+ // container view that we can hook up to without a widget.
+ // Don't create widgets for ResourceDocs (external resources & svg images),
+ // because when they're displayed, they're painted into *another* document's
+ // widget.
+ if (!mDocument->IsResourceDoc() && (mParentWidget || !aContainerView)) {
+ // pass in a native widget to be the parent widget ONLY if the view
+ // hierarchy will stand alone. otherwise the view will find its own parent
+ // widget and "do the right thing" to establish a parent/child widget
+ // relationship
+ widget::InitData initData;
+ widget::InitData* initDataPtr;
+ if (!mParentWidget) {
+ initDataPtr = &initData;
+ initData.mWindowType = widget::WindowType::Invisible;
+ } else {
+ initDataPtr = nullptr;
+ }
+
+ if (shouldAttach) {
+ // Reuse the top level parent widget.
+ rv = view->AttachToTopLevelWidget(mParentWidget);
+ mAttachedToParent = true;
+ } else if (!aContainerView && mParentWidget) {
+ rv = view->CreateWidgetForParent(mParentWidget, initDataPtr, true, false);
+ } else {
+ rv = view->CreateWidget(initDataPtr, true, false);
+ }
+ if (NS_FAILED(rv)) return rv;
+ }
+
+ // Setup hierarchical relationship in view manager
+ mViewManager->SetRootView(view);
+
+ mWindow = view->GetWidget();
+
+ // This SetFocus is necessary so the Arrow Key and Page Key events
+ // go to the scrolled view as soon as the Window is created instead of going
+ // to the browser window (this enables keyboard scrolling of the document)
+ // mWindow->SetFocus();
+
+ return rv;
+}
+
+void nsDocumentViewer::DetachFromTopLevelWidget() {
+ if (mViewManager) {
+ nsView* oldView = mViewManager->GetRootView();
+ if (oldView && oldView->IsAttachedToTopLevel()) {
+ oldView->DetachFromTopLevelWidget();
+ }
+ }
+ mAttachedToParent = false;
+}
+
+nsView* nsDocumentViewer::FindContainerView() {
+ if (!mContainer) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ nsCOMPtr<nsPIDOMWindowOuter> pwin(docShell->GetWindow());
+ if (!pwin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<Element> containerElement = pwin->GetFrameElementInternal();
+ if (!containerElement) {
+ return nullptr;
+ }
+
+ nsIFrame* subdocFrame = containerElement->GetPrimaryFrame();
+ if (!subdocFrame) {
+ // XXX Silenced by default in bug 1175289
+ LAYOUT_WARNING("Subdocument container has no frame");
+ return nullptr;
+ }
+
+ // Check subdocFrame just to be safe. If this somehow fails we treat that as
+ // display:none, the document is not displayed.
+ if (!subdocFrame->IsSubDocumentFrame()) {
+ NS_WARNING_ASSERTION(subdocFrame->Type() == LayoutFrameType::None,
+ "Subdocument container has non-subdocument frame");
+ return nullptr;
+ }
+
+ NS_ASSERTION(subdocFrame->GetView(), "Subdoc frames must have views");
+ return static_cast<nsSubDocumentFrame*>(subdocFrame)->EnsureInnerView();
+}
+
+nsresult nsDocumentViewer::CreateDeviceContext(nsView* aContainerView) {
+ MOZ_ASSERT(!mPresShell && !mWindow,
+ "This will screw up our existing presentation");
+ MOZ_ASSERT(mDocument, "Gotta have a document here");
+
+ Document* doc = mDocument->GetDisplayDocument();
+ if (doc) {
+ NS_ASSERTION(!aContainerView,
+ "External resource document embedded somewhere?");
+ // We want to use our display document's device context if possible
+ nsPresContext* ctx = doc->GetPresContext();
+ if (ctx) {
+ mDeviceContext = ctx->DeviceContext();
+ return NS_OK;
+ }
+ }
+
+ // Create a device context even if we already have one, since our widget
+ // might have changed.
+ nsIWidget* widget = nullptr;
+ if (aContainerView) {
+ widget = aContainerView->GetNearestWidget(nullptr);
+ }
+ if (!widget) {
+ widget = mParentWidget;
+ }
+ if (widget) {
+ widget = widget->GetTopLevelWidget();
+ }
+
+ mDeviceContext = new nsDeviceContext();
+ mDeviceContext->Init(widget);
+ return NS_OK;
+}
+
+// Return the selection for the document. Note that text fields have their
+// own selection, which cannot be accessed with this method.
+mozilla::dom::Selection* nsDocumentViewer::GetDocumentSelection() {
+ if (!mPresShell) {
+ return nullptr;
+ }
+
+ return mPresShell->GetCurrentSelection(SelectionType::eNormal);
+}
+
+/* ============================================================================
+ * nsIDocumentViewerEdit
+ * ============================================================================
+ */
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::ClearSelection() {
+ // use nsCopySupport::GetSelectionForCopy() ?
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult rv;
+ selection->CollapseToStart(rv);
+ return rv.StealNSResult();
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::SelectAll() {
+ // XXX this is a temporary implementation copied from nsWebShell
+ // for now. I think Document and friends should have some helper
+ // functions to make this easier.
+
+ // use nsCopySupport::GetSelectionForCopy() ?
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mDocument) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsINode> bodyNode;
+ if (mDocument->IsHTMLOrXHTML()) {
+ // XXXbz why not just do GetBody() for all documents, then GetRootElement()
+ // if GetBody() is null?
+ bodyNode = mDocument->GetBody();
+ } else {
+ bodyNode = mDocument->GetRootElement();
+ }
+ if (!bodyNode) return NS_ERROR_FAILURE;
+
+ ErrorResult err;
+ selection->RemoveAllRanges(err);
+ if (err.Failed()) {
+ return err.StealNSResult();
+ }
+
+ mozilla::dom::Selection::AutoUserInitiated userSelection(selection);
+ selection->SelectAllChildren(*bodyNode, err);
+ return err.StealNSResult();
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopySelection() {
+ RefPtr<PresShell> presShell = mPresShell;
+ nsCopySupport::FireClipboardEvent(eCopy, nsIClipboard::kGlobalClipboard,
+ presShell, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopyLinkLocation() {
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsINode> node = GetPopupLinkNode();
+ // make noise if we're not in a link
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ nsCOMPtr<dom::Element> elm(do_QueryInterface(node));
+ NS_ENSURE_TRUE(elm, NS_ERROR_FAILURE);
+
+ nsAutoString locationText;
+ nsContentUtils::GetLinkLocation(elm, locationText);
+ if (locationText.IsEmpty()) return NS_ERROR_FAILURE;
+
+ nsresult rv = NS_OK;
+ nsCOMPtr<nsIClipboardHelper> clipboard(
+ do_GetService("@mozilla.org/widget/clipboardhelper;1", &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // copy the href onto the clipboard
+ return clipboard->CopyString(locationText);
+}
+
+NS_IMETHODIMP nsDocumentViewer::CopyImage(int32_t aCopyFlags) {
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ nsCOMPtr<nsIImageLoadingContent> node = GetPopupImageNode();
+ // make noise if we're not in an image
+ NS_ENSURE_TRUE(node, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsILoadContext> loadContext(mContainer);
+ return nsCopySupport::ImageCopy(node, loadContext, aCopyFlags);
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetCopyable(bool* aCopyable) {
+ NS_ENSURE_ARG_POINTER(aCopyable);
+ *aCopyable = nsCopySupport::CanCopy(mDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetContents(const char* mimeType,
+ bool selectionOnly,
+ nsAString& aOutValue) {
+ aOutValue.Truncate();
+
+ NS_ENSURE_TRUE(mPresShell, NS_ERROR_NOT_INITIALIZED);
+ NS_ENSURE_TRUE(mDocument, NS_ERROR_NOT_INITIALIZED);
+
+ // Now we have the selection. Make sure it's nonzero:
+ RefPtr<Selection> sel;
+ if (selectionOnly) {
+ sel = nsCopySupport::GetSelectionForCopy(mDocument);
+ NS_ENSURE_TRUE(sel, NS_ERROR_FAILURE);
+
+ if (NS_WARN_IF(sel->IsCollapsed())) {
+ return NS_OK;
+ }
+ }
+
+ // call the copy code
+ return nsCopySupport::GetContents(nsDependentCString(mimeType), 0, sel,
+ mDocument, aOutValue);
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetCanGetContents(bool* aCanGetContents) {
+ NS_ENSURE_ARG_POINTER(aCanGetContents);
+ *aCanGetContents = false;
+ NS_ENSURE_STATE(mDocument);
+ *aCanGetContents = nsCopySupport::CanCopy(mDocument);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::SetCommandNode(nsINode* aNode) {
+ Document* document = GetDocument();
+ NS_ENSURE_STATE(document);
+
+ nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
+ NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
+
+ nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
+ NS_ENSURE_STATE(root);
+
+ root->SetPopupNode(aNode);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetDeviceFullZoomForTest(float* aDeviceFullZoom) {
+ NS_ENSURE_ARG_POINTER(aDeviceFullZoom);
+ nsPresContext* pc = GetPresContext();
+ *aDeviceFullZoom = pc ? pc->GetDeviceFullZoom() : 1.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetAuthorStyleDisabled(bool aStyleDisabled) {
+ if (mPresShell) {
+ mPresShell->SetAuthorStyleDisabled(aStyleDisabled);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetAuthorStyleDisabled(bool* aStyleDisabled) {
+ if (mPresShell) {
+ *aStyleDisabled = mPresShell->GetAuthorStyleDisabled();
+ } else {
+ *aStyleDisabled = false;
+ }
+ return NS_OK;
+}
+
+/* [noscript,notxpcom] Encoding getHintCharset (); */
+NS_IMETHODIMP_(const Encoding*)
+nsDocumentViewer::GetReloadEncodingAndSource(int32_t* aSource) {
+ *aSource = mReloadEncodingSource;
+ if (kCharsetUninitialized == mReloadEncodingSource) {
+ return nullptr;
+ }
+ return mReloadEncoding;
+}
+
+NS_IMETHODIMP_(void)
+nsDocumentViewer::SetReloadEncodingAndSource(const Encoding* aEncoding,
+ int32_t aSource) {
+ MOZ_ASSERT(
+ aSource == kCharsetUninitialized ||
+ (aSource >=
+ kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII &&
+ aSource <=
+ kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII) ||
+ aSource == kCharsetFromFinalUserForcedAutoDetection);
+ mReloadEncoding = aEncoding;
+ mReloadEncodingSource = aSource;
+}
+
+NS_IMETHODIMP_(void)
+nsDocumentViewer::ForgetReloadEncoding() {
+ mReloadEncoding = nullptr;
+ mReloadEncodingSource = kCharsetUninitialized;
+}
+
+MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP nsDocumentViewer::GetContentSize(
+ int32_t aMaxWidth, int32_t aMaxHeight, int32_t aPrefWidth, int32_t* aWidth,
+ int32_t* aHeight) {
+ RefPtr<BrowsingContext> bc = mContainer->GetBrowsingContext();
+ NS_ENSURE_TRUE(bc, NS_ERROR_NOT_AVAILABLE);
+
+ // It's only valid to access this from a top frame. Doesn't work from
+ // sub-frames.
+ NS_ENSURE_TRUE(bc->IsTop(), NS_ERROR_FAILURE);
+
+ // Convert max-width/height and pref-width to app units.
+ if (aMaxWidth > 0) {
+ aMaxWidth = CSSPixel::ToAppUnits(aMaxWidth);
+ } else {
+ aMaxWidth = NS_UNCONSTRAINEDSIZE;
+ }
+ if (aMaxHeight > 0) {
+ aMaxHeight = CSSPixel::ToAppUnits(aMaxHeight);
+ } else {
+ aMaxHeight = NS_UNCONSTRAINEDSIZE;
+ }
+ if (aPrefWidth > 0) {
+ aPrefWidth = CSSPixel::ToAppUnits(aPrefWidth);
+ } else {
+ aPrefWidth = 0;
+ }
+
+ RefPtr<PresShell> presShell = GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ // Flush out all content and style updates. We can't use a resize reflow
+ // because it won't change some sizes that a style change reflow will.
+ mDocument->FlushPendingNotifications(FlushType::Layout);
+
+ nsIFrame* root = presShell->GetRootFrame();
+ NS_ENSURE_TRUE(root, NS_ERROR_FAILURE);
+
+ WritingMode wm = root->GetWritingMode();
+
+ nscoord prefISize;
+ {
+ const auto& constraints = presShell->GetWindowSizeConstraints();
+ aMaxHeight = std::min(aMaxHeight, constraints.mMaxSize.height);
+ aMaxWidth = std::min(aMaxWidth, constraints.mMaxSize.width);
+
+ UniquePtr<gfxContext> rcx(presShell->CreateReferenceRenderingContext());
+ const nscoord minISize = wm.IsVertical() ? constraints.mMinSize.height
+ : constraints.mMinSize.width;
+ const nscoord maxISize = wm.IsVertical() ? aMaxHeight : aMaxWidth;
+ if (aPrefWidth) {
+ prefISize = std::max(root->GetMinISize(rcx.get()), aPrefWidth);
+ } else {
+ prefISize = root->GetPrefISize(rcx.get());
+ }
+ prefISize = nsPresContext::RoundUpAppUnitsToCSSPixel(
+ std::max(minISize, std::min(prefISize, maxISize)));
+ }
+
+ // We should never intentionally get here with this sentinel value, but it's
+ // possible that a document with huge sizes might inadvertently have a
+ // prefISize that exactly matches NS_UNCONSTRAINEDSIZE.
+ // Just bail if that happens.
+ NS_ENSURE_TRUE(prefISize != NS_UNCONSTRAINEDSIZE, NS_ERROR_FAILURE);
+
+ nscoord height = wm.IsVertical() ? prefISize : aMaxHeight;
+ nscoord width = wm.IsVertical() ? aMaxWidth : prefISize;
+
+ presShell->ResizeReflow(width, height, ResizeReflowOptions::BSizeLimit);
+
+ RefPtr<nsPresContext> presContext = GetPresContext();
+ NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
+
+ // Protect against bogus returns here
+ nsRect shellArea = presContext->GetVisibleArea();
+ NS_ENSURE_TRUE(shellArea.width != NS_UNCONSTRAINEDSIZE &&
+ shellArea.height != NS_UNCONSTRAINEDSIZE,
+ NS_ERROR_FAILURE);
+
+ // Ceil instead of rounding here, so we can actually guarantee showing all the
+ // content.
+ *aWidth = std::ceil(CSSPixel::FromAppUnits(shellArea.width));
+ *aHeight = std::ceil(CSSPixel::FromAppUnits(shellArea.height));
+
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsDocViewerSelectionListener, nsISelectionListener)
+
+/*
+ * GetPopupNode, GetPopupLinkNode and GetPopupImageNode are helpers
+ * for the cmd_copyLink / cmd_copyImageLocation / cmd_copyImageContents family
+ * of commands. The focus controller stores the popup node, these retrieve
+ * them and munge appropriately. Note that we have to store the popup node
+ * rather than retrieving it from EventStateManager::GetFocusedContent because
+ * not all content (images included) can receive focus.
+ */
+
+already_AddRefed<nsINode> nsDocumentViewer::GetPopupNode() {
+ // get the document
+ Document* document = GetDocument();
+ NS_ENSURE_TRUE(document, nullptr);
+
+ // get the private dom window
+ nsCOMPtr<nsPIDOMWindowOuter> window(document->GetWindow());
+ NS_ENSURE_TRUE(window, nullptr);
+ if (window) {
+ nsCOMPtr<nsPIWindowRoot> root = window->GetTopWindowRoot();
+ NS_ENSURE_TRUE(root, nullptr);
+
+ // get the popup node
+ nsCOMPtr<nsINode> node = root->GetPopupNode();
+ if (!node) {
+ nsPIDOMWindowOuter* rootWindow = root->GetWindow();
+ if (rootWindow) {
+ nsCOMPtr<Document> rootDoc = rootWindow->GetExtantDoc();
+ if (rootDoc) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (pm) {
+ node = pm->GetLastTriggerPopupNode(rootDoc);
+ }
+ }
+ }
+ }
+ return node.forget();
+ }
+
+ return nullptr;
+}
+
+// GetPopupLinkNode: return popup link node or fail
+already_AddRefed<nsINode> nsDocumentViewer::GetPopupLinkNode() {
+ // find popup node
+ nsCOMPtr<nsINode> node = GetPopupNode();
+
+ // find out if we have a link in our ancestry
+ while (node) {
+ if (const auto* element = Element::FromNode(*node)) {
+ if (element->IsLink()) {
+ return node.forget();
+ }
+ }
+
+ // get our parent and keep trying...
+ node = node->GetParentNode();
+ }
+
+ // if we have no node, fail
+ return nullptr;
+}
+
+// GetPopupLinkNode: return popup image node or fail
+already_AddRefed<nsIImageLoadingContent> nsDocumentViewer::GetPopupImageNode() {
+ // find popup node
+ nsCOMPtr<nsINode> node = GetPopupNode();
+ nsCOMPtr<nsIImageLoadingContent> img = do_QueryInterface(node);
+ return img.forget();
+}
+
+/*
+ * XXX dr
+ * ------
+ * These two functions -- GetInLink and GetInImage -- are kind of annoying
+ * in that they only get called from the controller (in
+ * nsDOMWindowController::IsCommandEnabled). The actual construction of the
+ * context menus in communicator (nsContextMenu.js) has its own, redundant
+ * tests. No big deal, but good to keep in mind if we ever clean context
+ * menus.
+ */
+
+NS_IMETHODIMP nsDocumentViewer::GetInLink(bool* aInLink) {
+#ifdef DEBUG_dr
+ printf("dr :: nsDocumentViewer::GetInLink\n");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aInLink);
+
+ // we're not in a link unless i say so
+ *aInLink = false;
+
+ // get the popup link
+ nsCOMPtr<nsINode> node = GetPopupLinkNode();
+ if (!node) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // if we made it here, we're in a link
+ *aInLink = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::GetInImage(bool* aInImage) {
+#ifdef DEBUG_dr
+ printf("dr :: nsDocumentViewer::GetInImage\n");
+#endif
+
+ NS_ENSURE_ARG_POINTER(aInImage);
+
+ // we're not in an image unless i say so
+ *aInImage = false;
+
+ // get the popup image
+ nsCOMPtr<nsIImageLoadingContent> node = GetPopupImageNode();
+ if (!node) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Make sure there is a URI assigned. This allows <input type="image"> to
+ // be an image but rejects other <input> types. This matches what
+ // nsContextMenu.js does.
+ nsCOMPtr<nsIURI> uri;
+ node->GetCurrentURI(getter_AddRefs(uri));
+ if (uri) {
+ // if we made it here, we're in an image
+ *aInImage = true;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocViewerSelectionListener::NotifySelectionChanged(
+ Document*, Selection*, int16_t aReason, int32_t aAmount) {
+ if (!mDocViewer) {
+ return NS_OK;
+ }
+
+ // get the selection state
+ RefPtr<mozilla::dom::Selection> selection =
+ mDocViewer->GetDocumentSelection();
+ if (!selection) {
+ return NS_ERROR_FAILURE;
+ }
+
+ Document* theDoc = mDocViewer->GetDocument();
+ if (!theDoc) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsPIDOMWindowOuter> domWindow = theDoc->GetWindow();
+ if (!domWindow) return NS_ERROR_FAILURE;
+
+ bool selectionCollapsed = selection->IsCollapsed();
+ // We only call UpdateCommands when the selection changes from collapsed to
+ // non-collapsed or vice versa, however we skip the initializing collapse. We
+ // might need another update string for simple selection changes, but that
+ // would be expenseive.
+ if (mSelectionWasCollapsed != selectionCollapsed) {
+ domWindow->UpdateCommands(u"select"_ns);
+ mSelectionWasCollapsed = selectionCollapsed;
+ }
+
+ return NS_OK;
+}
+
+// nsDocViewerFocusListener
+NS_IMPL_ISUPPORTS(nsDocViewerFocusListener, nsIDOMEventListener)
+
+nsresult nsDocViewerFocusListener::HandleEvent(Event* aEvent) {
+ NS_ENSURE_STATE(mDocViewer);
+
+ RefPtr<PresShell> presShell = mDocViewer->GetPresShell();
+ NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
+
+ RefPtr<nsFrameSelection> selection =
+ presShell->GetLastFocusedFrameSelection();
+ NS_ENSURE_TRUE(selection, NS_ERROR_FAILURE);
+ auto selectionStatus = selection->GetDisplaySelection();
+ nsAutoString eventType;
+ aEvent->GetType(eventType);
+ if (eventType.EqualsLiteral("focus")) {
+ // If selection was disabled, re-enable it.
+ if (selectionStatus == nsISelectionController::SELECTION_DISABLED ||
+ selectionStatus == nsISelectionController::SELECTION_HIDDEN) {
+ selection->SetDisplaySelection(nsISelectionController::SELECTION_ON);
+ selection->RepaintSelection(SelectionType::eNormal);
+ }
+ // See EditorBase::FinalizeSelection. This fixes up the case where focus
+ // left the editor's selection but returned to something else.
+ if (selection != presShell->ConstFrameSelection()) {
+ RefPtr<Document> doc = presShell->GetDocument();
+ const bool selectionMatchesFocus =
+ selection->GetLimiter() &&
+ selection->GetLimiter()->GetChromeOnlyAccessSubtreeRootParent() ==
+ doc->GetUnretargetedFocusedContent();
+ if (NS_WARN_IF(!selectionMatchesFocus)) {
+ presShell->FrameSelectionWillLoseFocus(*selection);
+ presShell->SelectionWillTakeFocus();
+ }
+ }
+ } else {
+ MOZ_ASSERT(eventType.EqualsLiteral("blur"), "Unexpected event type");
+ // If selection was on, disable it.
+ if (selectionStatus == nsISelectionController::SELECTION_ON ||
+ selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
+ selection->SetDisplaySelection(
+ nsISelectionController::SELECTION_DISABLED);
+ selection->RepaintSelection(SelectionType::eNormal);
+ }
+ }
+
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ * From nsIWebBrowserPrint
+ */
+
+#ifdef NS_PRINTING
+
+NS_IMETHODIMP
+nsDocumentViewer::Print(nsIPrintSettings* aPrintSettings,
+ RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener* aWebProgressListener) {
+ if (NS_WARN_IF(!mContainer)) {
+ PR_PL(("Container was destroyed yet we are still trying to use it!"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(!mDocument) || NS_WARN_IF(!mDeviceContext)) {
+ PR_PL(("Can't Print without a document and a device context"));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (NS_WARN_IF(mPrintJob && mPrintJob->GetIsPrinting())) {
+ // If we are printing another URL, then exit.
+ // The reason we check here is because this method can be called while
+ // another is still in here (the printing dialog is a good example). the
+ // only time we can print more than one job at a time is the regression
+ // tests.
+ nsresult rv = NS_ERROR_NOT_AVAILABLE;
+ RefPtr<nsPrintJob>(mPrintJob)->FirePrintingErrorEvent(rv);
+ return rv;
+ }
+
+ OnDonePrinting();
+
+ // Note: mContainer and mDocument are known to be non-null via null-checks
+ // earlier in this function.
+ // TODO(dholbert) Do we need to bother with this stack-owned local RefPtr?
+ // (Is there an edge case where it's needed to keep the nsPrintJob alive?)
+ RefPtr<nsPrintJob> printJob =
+ new nsPrintJob(*this, *mContainer, *mDocument,
+ float(AppUnitsPerCSSInch()) /
+ float(mDeviceContext->AppUnitsPerDevPixel()));
+ mPrintJob = printJob;
+
+ nsresult rv = printJob->Print(*mDocument, aPrintSettings, aRemotePrintJob,
+ aWebProgressListener);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OnDonePrinting();
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::PrintPreview(nsIPrintSettings* aPrintSettings,
+ nsIWebProgressListener* aWebProgressListener,
+ PrintPreviewResolver&& aCallback) {
+# ifdef NS_PRINT_PREVIEW
+ RefPtr<Document> doc = mDocument.get();
+ NS_ENSURE_STATE(doc);
+
+ if (NS_WARN_IF(GetIsPrinting())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(mContainer);
+ if (NS_WARN_IF(!docShell) || NS_WARN_IF(!mDeviceContext)) {
+ PR_PL(("Can't Print Preview without device context and docshell"));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ENSURE_STATE(!GetIsPrinting());
+ // beforeprint event may have caused ContentViewer to be shutdown.
+ NS_ENSURE_STATE(mContainer);
+ NS_ENSURE_STATE(mDeviceContext);
+
+ OnDonePrinting();
+
+ // Note: mContainer and doc are known to be non-null via null-checks earlier
+ // in this function.
+ // TODO(dholbert) Do we need to bother with this stack-owned local RefPtr?
+ // (Is there an edge case where it's needed to keep the nsPrintJob alive?)
+ RefPtr<nsPrintJob> printJob =
+ new nsPrintJob(*this, *mContainer, *doc,
+ float(AppUnitsPerCSSInch()) /
+ float(mDeviceContext->AppUnitsPerDevPixel()));
+ mPrintJob = printJob;
+
+ nsresult rv = printJob->PrintPreview(
+ *doc, aPrintSettings, aWebProgressListener, std::move(aCallback));
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ OnDonePrinting();
+ }
+ return rv;
+# else
+ return NS_ERROR_FAILURE;
+# endif // NS_PRINT_PREVIEW
+}
+
+static const nsIFrame* GetTargetPageFrame(int32_t aTargetPageNum,
+ nsPageSequenceFrame* aSequenceFrame) {
+ MOZ_ASSERT(aTargetPageNum > 0 &&
+ aTargetPageNum <=
+ aSequenceFrame->PrincipalChildList().GetLength());
+ return aSequenceFrame->PrincipalChildList().FrameAt(aTargetPageNum - 1);
+}
+
+// Calculate the scroll position where the center of |aFrame| is positioned at
+// the center of |aScrollable|'s scroll port for the print preview.
+// So what we do for that is;
+// 1) Calculate the position of the center of |aFrame| in the print preview
+// coordinates.
+// 2) Reduce the half height of the scroll port from the result of 1.
+static nscoord ScrollPositionForFrame(const nsIFrame* aFrame,
+ nsIScrollableFrame* aScrollable,
+ float aPreviewScale) {
+ // Note that even if the computed scroll position is out of the range of
+ // the scroll port, it gets clamped in nsIScrollableFrame::ScrollTo.
+ return nscoord(aPreviewScale * aFrame->GetRect().Center().y -
+ float(aScrollable->GetScrollPortRect().height) / 2.0f);
+}
+
+//----------------------------------------------------------------------
+NS_IMETHODIMP
+nsDocumentViewer::PrintPreviewScrollToPage(int16_t aType, int32_t aPageNum) {
+ if (!GetIsPrintPreview() || mPrintJob->GetIsCreatingPrintPreview())
+ return NS_ERROR_FAILURE;
+
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (!sf) return NS_OK;
+
+ auto [seqFrame, sheetCount] = mPrintJob->GetSeqFrameAndCountSheets();
+ Unused << sheetCount;
+ if (!seqFrame) {
+ return NS_ERROR_FAILURE;
+ }
+
+ float previewScale = seqFrame->GetPrintPreviewScale();
+
+ nsPoint dest = sf->GetScrollPosition();
+
+ switch (aType) {
+ case nsIWebBrowserPrint::PRINTPREVIEW_HOME:
+ dest.y = 0;
+ break;
+ case nsIWebBrowserPrint::PRINTPREVIEW_END:
+ dest.y = sf->GetScrollRange().YMost();
+ break;
+ case nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE:
+ case nsIWebBrowserPrint::PRINTPREVIEW_NEXT_PAGE: {
+ auto [currentFrame, currentSheetNumber] = GetCurrentSheetFrameAndNumber();
+ Unused << currentSheetNumber;
+ if (!currentFrame) {
+ return NS_OK;
+ }
+
+ const nsIFrame* targetFrame = nullptr;
+ if (aType == nsIWebBrowserPrint::PRINTPREVIEW_PREV_PAGE) {
+ targetFrame = currentFrame->GetPrevInFlow();
+ } else {
+ targetFrame = currentFrame->GetNextInFlow();
+ }
+ if (!targetFrame) {
+ return NS_OK;
+ }
+
+ dest.y = ScrollPositionForFrame(targetFrame, sf, previewScale);
+ break;
+ }
+ case nsIWebBrowserPrint::PRINTPREVIEW_GOTO_PAGENUM: {
+ if (aPageNum <= 0 || aPageNum > sheetCount) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ const nsIFrame* targetFrame = GetTargetPageFrame(aPageNum, seqFrame);
+ MOZ_ASSERT(targetFrame);
+
+ dest.y = ScrollPositionForFrame(targetFrame, sf, previewScale);
+ break;
+ }
+ default:
+ return NS_ERROR_INVALID_ARG;
+ break;
+ }
+
+ sf->ScrollTo(dest, ScrollMode::Instant);
+
+ return NS_OK;
+}
+
+std::tuple<const nsIFrame*, int32_t>
+nsDocumentViewer::GetCurrentSheetFrameAndNumber() const {
+ MOZ_ASSERT(mPrintJob);
+ MOZ_ASSERT(GetIsPrintPreview() && !mPrintJob->GetIsCreatingPrintPreview());
+
+ // in PP mPrtPreview->mPrintObject->mSeqFrame is null
+ auto [seqFrame, sheetCount] = mPrintJob->GetSeqFrameAndCountSheets();
+ Unused << sheetCount;
+ if (!seqFrame) {
+ return {nullptr, 0};
+ }
+
+ nsIScrollableFrame* sf = mPresShell->GetRootScrollFrameAsScrollable();
+ if (!sf) {
+ // No scrollable contents, returns 1 even if there are multiple sheets.
+ return {seqFrame->PrincipalChildList().FirstChild(), 1};
+ }
+
+ nsPoint currentScrollPosition = sf->GetScrollPosition();
+ float halfwayPoint =
+ currentScrollPosition.y + float(sf->GetScrollPortRect().height) / 2.0f;
+ float lastDistanceFromHalfwayPoint = std::numeric_limits<float>::max();
+ int32_t sheetNumber = 0;
+ const nsIFrame* currentSheet = nullptr;
+ float previewScale = seqFrame->GetPrintPreviewScale();
+ for (const nsIFrame* sheetFrame : seqFrame->PrincipalChildList()) {
+ nsRect sheetRect = sheetFrame->GetRect();
+ sheetNumber++;
+ currentSheet = sheetFrame;
+
+ float bottomOfSheet = sheetRect.YMost() * previewScale;
+ if (bottomOfSheet < halfwayPoint) {
+ // If the bottom of the sheet is not yet over the halfway point, iterate
+ // the next frame to see if the next frame is over the halfway point and
+ // compare the distance from the halfway point.
+ lastDistanceFromHalfwayPoint = halfwayPoint - bottomOfSheet;
+ continue;
+ }
+
+ float topOfSheet = sheetRect.Y() * previewScale;
+ if (topOfSheet <= halfwayPoint) {
+ // If the top of the sheet is not yet over the halfway point or on the
+ // point, it's the current sheet.
+ break;
+ }
+
+ // Now the sheet rect is completely over the halfway point, compare the
+ // distances from the halfway point.
+ if ((topOfSheet - halfwayPoint) >= lastDistanceFromHalfwayPoint) {
+ // If the previous sheet distance is less than or equal to the current
+ // sheet distance, choose the previous one as the current.
+ sheetNumber--;
+ MOZ_ASSERT(sheetNumber > 0);
+ currentSheet = currentSheet->GetPrevInFlow();
+ MOZ_ASSERT(currentSheet);
+ }
+ break;
+ }
+
+ MOZ_ASSERT(sheetNumber <= sheetCount);
+ return {currentSheet, sheetNumber};
+}
+
+// XXXdholbert As noted in nsIWebBrowserPrint.idl, this API (the IDL attr
+// 'printPreviewCurrentPageNumber') is misnamed and needs s/Page/Sheet/. See
+// bug 1669762.
+NS_IMETHODIMP
+nsDocumentViewer::GetPrintPreviewCurrentPageNumber(int32_t* aNumber) {
+ NS_ENSURE_ARG_POINTER(aNumber);
+ NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
+ if (!GetIsPrintPreview() || mPrintJob->GetIsCreatingPrintPreview()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ auto [currentFrame, currentSheetNumber] = GetCurrentSheetFrameAndNumber();
+ Unused << currentFrame;
+ if (!currentSheetNumber) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aNumber = currentSheetNumber;
+
+ return NS_OK;
+}
+
+// XXX This always returns false for subdocuments
+NS_IMETHODIMP
+nsDocumentViewer::GetDoingPrint(bool* aDoingPrint) {
+ NS_ENSURE_ARG_POINTER(aDoingPrint);
+
+ // XXX shouldn't this be GetDoingPrint() ?
+ *aDoingPrint = mPrintJob ? mPrintJob->CreatedForPrintPreview() : false;
+ return NS_OK;
+}
+
+// XXX This always returns false for subdocuments
+NS_IMETHODIMP
+nsDocumentViewer::GetDoingPrintPreview(bool* aDoingPrintPreview) {
+ NS_ENSURE_ARG_POINTER(aDoingPrintPreview);
+
+ *aDoingPrintPreview = mPrintJob ? mPrintJob->CreatedForPrintPreview() : false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::ExitPrintPreview() {
+ NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
+
+ if (GetIsPrinting()) {
+ // Block exiting the print preview window if we're in the middle of an
+ // actual print.
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!GetIsPrintPreview()) {
+ NS_ERROR("Wow, we should never get here!");
+ return NS_OK;
+ }
+
+# ifdef NS_PRINT_PREVIEW
+ mPrintJob->Destroy();
+ mPrintJob = nullptr;
+
+ // Since the print preview implementation discards the window that was used
+ // to show the print preview, we skip certain cleanup that we would otherwise
+ // want to do. Specifically, we do not call `SetIsPrintPreview(false)` to
+ // unblock navigation, we do not call `SetOverrideDPPX` to reset the
+ // devicePixelRatio, and we do not call `Show` to make such changes take
+ // affect.
+# endif // NS_PRINT_PREVIEW
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetRawNumPages(int32_t* aRawNumPages) {
+ NS_ENSURE_ARG_POINTER(aRawNumPages);
+ NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
+
+ *aRawNumPages = mPrintJob->GetRawNumPages();
+ return *aRawNumPages > 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+// XXXdholbert As noted in nsIWebBrowserPrint.idl, this API (the IDL attr
+// 'printPreviewNumPages') is misnamed and needs s/Page/Sheet/.
+// See bug 1669762.
+NS_IMETHODIMP
+nsDocumentViewer::GetPrintPreviewNumPages(int32_t* aPrintPreviewNumPages) {
+ NS_ENSURE_ARG_POINTER(aPrintPreviewNumPages);
+ NS_ENSURE_TRUE(mPrintJob, NS_ERROR_FAILURE);
+ *aPrintPreviewNumPages = mPrintJob->GetPrintPreviewNumSheets();
+ return *aPrintPreviewNumPages > 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+//----------------------------------------------------------------------------------
+// Printing/Print Preview Helpers
+//----------------------------------------------------------------------------------
+
+//----------------------------------------------------------------------------------
+// Walks the document tree and tells each DocShell whether Printing/PP is
+// happening
+#endif // NS_PRINTING
+
+bool nsDocumentViewer::ShouldAttachToTopLevel() {
+ if (!mParentWidget) {
+ return false;
+ }
+
+ // We always attach when using puppet widgets
+ if (nsIWidget::UsePuppetWidgets()) {
+ return true;
+ }
+
+ // FIXME(emilio): Can we unify this between macOS and aother platforms?
+#ifdef XP_MACOSX
+ return false;
+#else
+# ifdef DEBUG
+ nsIWidgetListener* parentListener = mParentWidget->GetWidgetListener();
+ MOZ_ASSERT(!parentListener || !parentListener->GetView(),
+ "Expect a top level widget");
+# endif
+ return true;
+#endif
+}
+
+//------------------------------------------------------------
+// XXX this always returns false for subdocuments
+bool nsDocumentViewer::GetIsPrinting() const {
+#ifdef NS_PRINTING
+ if (mPrintJob) {
+ return mPrintJob->GetIsPrinting();
+ }
+#endif
+ return false;
+}
+
+//------------------------------------------------------------
+// The PrintJob holds the current value
+// this called from inside the DocViewer.
+// XXX it always returns false for subdocuments
+bool nsDocumentViewer::GetIsPrintPreview() const {
+#ifdef NS_PRINTING
+ return mPrintJob && mPrintJob->CreatedForPrintPreview();
+#else
+ return false;
+#endif
+}
+
+//------------------------------------------------------------
+// Notification from the PrintJob of the current PP status
+void nsDocumentViewer::SetIsPrintPreview(bool aIsPrintPreview) {
+ // Protect against pres shell destruction running scripts.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (!aIsPrintPreview) {
+ InvalidatePotentialSubDocDisplayItem();
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+ mWindow = nullptr;
+ mViewManager = nullptr;
+ mPresContext = nullptr;
+ mPresShell = nullptr;
+ }
+}
+
+//----------------------------------------------------------------------------------
+// nsIDocumentViewerPrint IFace
+//----------------------------------------------------------------------------------
+
+//------------------------------------------------------------
+void nsDocumentViewer::IncrementDestroyBlockedCount() {
+ ++mDestroyBlockedCount;
+}
+
+void nsDocumentViewer::DecrementDestroyBlockedCount() {
+ --mDestroyBlockedCount;
+}
+
+//------------------------------------------------------------
+// This called ONLY when printing has completed and the DV
+// is being notified that it should get rid of the nsPrintJob.
+//
+// BUT, if we are in Print Preview then we want to ignore the
+// notification (we do not get rid of the nsPrintJob)
+//
+// One small caveat:
+// This IS called from two places in this module for cleaning
+// up when an error occurred during the start up printing
+// and print preview
+//
+void nsDocumentViewer::OnDonePrinting() {
+#if defined(NS_PRINTING) && defined(NS_PRINT_PREVIEW)
+ // If Destroy() has been called during calling nsPrintJob::Print() or
+ // nsPrintJob::PrintPreview(), mPrintJob is already nullptr here.
+ // So, the following clean up does nothing in such case.
+ // (Do we need some of this for that case?)
+ if (mPrintJob) {
+ RefPtr<nsPrintJob> printJob = std::move(mPrintJob);
+ if (GetIsPrintPreview()) {
+ printJob->DestroyPrintingData();
+ } else {
+ printJob->Destroy();
+ }
+
+ // We are done printing, now clean up.
+ //
+ // For non-print-preview jobs, we are actually responsible for cleaning up
+ // our whole <browser> or window (see the OPEN_PRINT_BROWSER code), so gotta
+ // run window.close(), which will take care of this.
+ //
+ // For print preview jobs the front-end code is responsible for cleaning the
+ // UI.
+ if (!printJob->CreatedForPrintPreview()) {
+ if (mContainer) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = mContainer->GetWindow()) {
+ win->Close();
+ }
+ }
+ } else if (mClosingWhilePrinting) {
+ if (mDocument) {
+ mDocument->Destroy();
+ mDocument = nullptr;
+ }
+ mClosingWhilePrinting = false;
+ }
+ }
+#endif // NS_PRINTING && NS_PRINT_PREVIEW
+}
+
+NS_IMETHODIMP nsDocumentViewer::SetPrintSettingsForSubdocument(
+ nsIPrintSettings* aPrintSettings, RemotePrintJobChild* aRemotePrintJob) {
+#ifdef NS_PRINTING
+ {
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ if (mPresContext) {
+ DestroyPresContext();
+ }
+
+ MOZ_ASSERT(!mPresContext);
+ MOZ_ASSERT(!mPresShell);
+
+ if (MOZ_UNLIKELY(!mDocument)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ RefPtr<nsDeviceContextSpecProxy> devspec =
+ new nsDeviceContextSpecProxy(aRemotePrintJob);
+ nsresult rv = devspec->Init(aPrintSettings, /* aIsPrintPreview = */ true);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mDeviceContext = new nsDeviceContext();
+ rv = mDeviceContext->InitForPrinting(devspec);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mPresContext = CreatePresContext(
+ mDocument, nsPresContext::eContext_PrintPreview, FindContainerView());
+ mPresContext->SetPrintSettings(aPrintSettings);
+ rv = mPresContext->Init(mDeviceContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = MakeWindow(nsSize(mPresContext->DevPixelsToAppUnits(mBounds.width),
+ mPresContext->DevPixelsToAppUnits(mBounds.height)),
+ FindContainerView());
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ MOZ_TRY(InitPresentationStuff(true));
+ }
+
+ RefPtr<PresShell> shell = mPresShell;
+ shell->FlushPendingNotifications(FlushType::Layout);
+#endif
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsDocumentViewer::SetPageModeForTesting(
+ bool aPageMode, nsIPrintSettings* aPrintSettings) {
+ // XXX Page mode is only partially working; it's currently used for
+ // reftests that require a paginated context
+ mIsPageMode = aPageMode;
+
+ // The DestroyPresShell call requires a script blocker, since the
+ // PresShell::Destroy call it does can cause scripts to run, which could
+ // re-entrantly call methods on the nsDocumentViewer.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ if (mPresContext) {
+ DestroyPresContext();
+ }
+
+ mViewManager = nullptr;
+ mWindow = nullptr;
+
+ NS_ENSURE_STATE(mDocument);
+ if (aPageMode) {
+ mPresContext = CreatePresContext(
+ mDocument, nsPresContext::eContext_PageLayout, FindContainerView());
+ NS_ENSURE_TRUE(mPresContext, NS_ERROR_OUT_OF_MEMORY);
+ mPresContext->SetPaginatedScrolling(true);
+ mPresContext->SetPrintSettings(aPrintSettings);
+ nsresult rv = mPresContext->Init(mDeviceContext);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+ NS_ENSURE_SUCCESS(InitInternal(mParentWidget, nullptr, nullptr, mBounds, true,
+ false, false),
+ NS_ERROR_FAILURE);
+
+ Show();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetHistoryEntry(nsISHEntry** aHistoryEntry) {
+ NS_IF_ADDREF(*aHistoryEntry = mSHEntry);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsTabModalPromptAllowed(bool* aAllowed) {
+ *aAllowed = !mHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::GetIsHidden(bool* aHidden) {
+ *aHidden = mHidden;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDocumentViewer::SetIsHidden(bool aHidden) {
+ mHidden = aHidden;
+ return NS_OK;
+}
+
+void nsDocumentViewer::DestroyPresShell() {
+ // We assert this because destroying the pres shell could otherwise cause
+ // re-entrancy into nsDocumentViewer methods, and all callers of
+ // DestroyPresShell need to do other cleanup work afterwards before it
+ // is safe for those re-entrant method calls to be made.
+ MOZ_ASSERT(!nsContentUtils::IsSafeToRunScript(),
+ "DestroyPresShell must only be called when scripts are blocked");
+
+ // Break circular reference (or something)
+ mPresShell->EndObservingDocument();
+
+ RefPtr<mozilla::dom::Selection> selection = GetDocumentSelection();
+ if (selection && mSelectionListener)
+ selection->RemoveSelectionListener(mSelectionListener);
+
+ mPresShell->Destroy();
+ mPresShell = nullptr;
+}
+
+void nsDocumentViewer::InvalidatePotentialSubDocDisplayItem() {
+ if (mViewManager) {
+ if (nsView* rootView = mViewManager->GetRootView()) {
+ if (nsView* rootViewParent = rootView->GetParent()) {
+ if (nsView* subdocview = rootViewParent->GetParent()) {
+ if (nsIFrame* f = subdocview->GetFrame()) {
+ if (nsSubDocumentFrame* s = do_QueryFrame(f)) {
+ s->MarkNeedsDisplayItemRebuild();
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void nsDocumentViewer::DestroyPresContext() {
+ InvalidatePotentialSubDocDisplayItem();
+ mPresContext = nullptr;
+}
+
+void nsDocumentViewer::SetPrintPreviewPresentation(nsViewManager* aViewManager,
+ nsPresContext* aPresContext,
+ PresShell* aPresShell) {
+ // Protect against pres shell destruction running scripts and re-entrantly
+ // creating a new presentation.
+ nsAutoScriptBlocker scriptBlocker;
+
+ if (mPresShell) {
+ DestroyPresShell();
+ }
+
+ mWindow = nullptr;
+ mViewManager = aViewManager;
+ mPresContext = aPresContext;
+ mPresShell = aPresShell;
+
+ if (ShouldAttachToTopLevel()) {
+ DetachFromTopLevelWidget();
+ nsView* rootView = mViewManager->GetRootView();
+ rootView->AttachToTopLevelWidget(mParentWidget);
+ mAttachedToParent = true;
+ }
+}
+
+// Fires the "document-shown" event so that interested parties are aware of it.
+NS_IMETHODIMP
+nsDocumentShownDispatcher::Run() {
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (observerService) {
+ observerService->NotifyObservers(ToSupports(mDocument), "document-shown",
+ nullptr);
+ }
+ return NS_OK;
+}
diff --git a/layout/base/nsFrameManager.cpp b/layout/base/nsFrameManager.cpp
new file mode 100644
index 0000000000..87328ccb80
--- /dev/null
+++ b/layout/base/nsFrameManager.cpp
@@ -0,0 +1,250 @@
+/* -*- 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/. */
+
+/* storage of the frame tree and information about it */
+
+#include "nsFrameManager.h"
+
+#include "nscore.h"
+#include "nsCOMPtr.h"
+#include "plhash.h"
+#include "nsPlaceholderFrame.h"
+#include "nsGkAtoms.h"
+#include "nsILayoutHistoryState.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresState.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/Document.h"
+
+#include "nsError.h"
+#include "nsAbsoluteContainingBlock.h"
+#include "ChildIterator.h"
+
+#include "GeckoProfiler.h"
+#include "nsIStatefulFrame.h"
+#include "nsContainerFrame.h"
+#include "nsWindowSizes.h"
+
+#include "mozilla/MemoryReporting.h"
+
+// #define DEBUG_UNDISPLAYED_MAP
+// #define DEBUG_DISPLAY_CONTENTS_MAP
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+//----------------------------------------------------------------------
+
+nsFrameManager::~nsFrameManager() {
+ NS_ASSERTION(!mPresShell, "nsFrameManager::Destroy never called");
+}
+
+void nsFrameManager::Destroy() {
+ NS_ASSERTION(mPresShell, "Frame manager already shut down.");
+
+ // Destroy the frame hierarchy.
+ mPresShell->SetIgnoreFrameDestruction(true);
+
+ if (mRootFrame) {
+ FrameDestroyContext context(mPresShell);
+ mRootFrame->Destroy(context);
+ mRootFrame = nullptr;
+ }
+
+ mPresShell = nullptr;
+}
+
+//----------------------------------------------------------------------
+void nsFrameManager::AppendFrames(nsContainerFrame* aParentFrame,
+ FrameChildListID aListID,
+ nsFrameList&& aFrameList) {
+ if (aParentFrame->IsAbsoluteContainer() &&
+ aListID == aParentFrame->GetAbsoluteListID()) {
+ aParentFrame->GetAbsoluteContainingBlock()->AppendFrames(
+ aParentFrame, aListID, std::move(aFrameList));
+ } else {
+ aParentFrame->AppendFrames(aListID, std::move(aFrameList));
+ }
+}
+
+void nsFrameManager::InsertFrames(nsContainerFrame* aParentFrame,
+ FrameChildListID aListID,
+ nsIFrame* aPrevFrame,
+ nsFrameList&& aFrameList) {
+ MOZ_ASSERT(
+ !aPrevFrame ||
+ (!aPrevFrame->GetNextContinuation() ||
+ (aPrevFrame->GetNextContinuation()->HasAnyStateBits(
+ NS_FRAME_IS_OVERFLOW_CONTAINER) &&
+ !aPrevFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER))),
+ "aPrevFrame must be the last continuation in its chain!");
+
+ if (aParentFrame->IsAbsoluteContainer() &&
+ aListID == aParentFrame->GetAbsoluteListID()) {
+ aParentFrame->GetAbsoluteContainingBlock()->InsertFrames(
+ aParentFrame, aListID, aPrevFrame, std::move(aFrameList));
+ } else {
+ aParentFrame->InsertFrames(aListID, aPrevFrame, nullptr,
+ std::move(aFrameList));
+ }
+}
+
+void nsFrameManager::RemoveFrame(DestroyContext& aContext,
+ FrameChildListID aListID,
+ nsIFrame* aOldFrame) {
+ // In case the reflow doesn't invalidate anything since it just leaves
+ // a gap where the old frame was, we invalidate it here. (This is
+ // reasonably likely to happen when removing a last child in a way
+ // that doesn't change the size of the parent.)
+ // This has to sure to invalidate the entire overflow rect; this
+ // is important in the presence of absolute positioning
+ aOldFrame->InvalidateFrameForRemoval();
+
+ NS_ASSERTION(!aOldFrame->GetPrevContinuation() ||
+ // exception for
+ // nsCSSFrameConstructor::RemoveFloatingFirstLetterFrames
+ aOldFrame->IsTextFrame(),
+ "Must remove first continuation.");
+ NS_ASSERTION(!(aOldFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ aOldFrame->GetPlaceholderFrame()),
+ "Must call RemoveFrame on placeholder for out-of-flows.");
+ nsContainerFrame* parentFrame = aOldFrame->GetParent();
+ if (parentFrame->IsAbsoluteContainer() &&
+ aListID == parentFrame->GetAbsoluteListID()) {
+ parentFrame->GetAbsoluteContainingBlock()->RemoveFrame(aContext, aListID,
+ aOldFrame);
+ } else {
+ parentFrame->RemoveFrame(aContext, aListID, aOldFrame);
+ }
+}
+
+//----------------------------------------------------------------------
+
+// Capture state for a given frame.
+// Accept a content id here, in some cases we may not have content (scroll
+// position)
+void nsFrameManager::CaptureFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState) {
+ if (!aFrame || !aState) {
+ NS_WARNING("null frame, or state");
+ return;
+ }
+
+ // Only capture state for stateful frames
+ nsIStatefulFrame* statefulFrame = do_QueryFrame(aFrame);
+ if (!statefulFrame) {
+ return;
+ }
+
+ // Capture the state, exit early if we get null (nothing to save)
+ UniquePtr<PresState> frameState = statefulFrame->SaveState();
+ if (!frameState) {
+ return;
+ }
+
+ // Generate the hash key to store the state under
+ // Exit early if we get empty key
+ nsAutoCString stateKey;
+ nsIContent* content = aFrame->GetContent();
+ Document* doc = content ? content->GetUncomposedDoc() : nullptr;
+ statefulFrame->GenerateStateKey(content, doc, stateKey);
+ if (stateKey.IsEmpty()) {
+ return;
+ }
+
+ // Store the state. aState owns frameState now.
+ aState->AddState(stateKey, std::move(frameState));
+}
+
+void nsFrameManager::CaptureFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState) {
+ MOZ_ASSERT(nullptr != aFrame && nullptr != aState,
+ "null parameters passed in");
+
+ CaptureFrameStateFor(aFrame, aState);
+
+ // Now capture state recursively for the frame hierarchy rooted at aFrame
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ if (child->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW)) {
+ // We'll pick it up when we get to its placeholder
+ continue;
+ }
+ // Make sure to walk through placeholders as needed, so that we
+ // save state for out-of-flows which may not be our descendants
+ // themselves but whose placeholders are our descendants.
+ CaptureFrameState(nsPlaceholderFrame::GetRealFrameFor(child), aState);
+ }
+ }
+}
+
+// Restore state for a given frame.
+// Accept a content id here, in some cases we may not have content (scroll
+// position)
+void nsFrameManager::RestoreFrameStateFor(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState) {
+ if (!aFrame || !aState) {
+ NS_WARNING("null frame or state");
+ return;
+ }
+
+ // Only restore state for stateful frames
+ nsIStatefulFrame* statefulFrame = do_QueryFrame(aFrame);
+ if (!statefulFrame) {
+ return;
+ }
+
+ // Generate the hash key the state was stored under
+ // Exit early if we get empty key
+ nsIContent* content = aFrame->GetContent();
+ // If we don't have content, we can't generate a hash
+ // key and there's probably no state information for us.
+ if (!content) {
+ return;
+ }
+
+ nsAutoCString stateKey;
+ Document* doc = content->GetUncomposedDoc();
+ statefulFrame->GenerateStateKey(content, doc, stateKey);
+ if (stateKey.IsEmpty()) {
+ return;
+ }
+
+ // Get the state from the hash
+ PresState* frameState = aState->GetState(stateKey);
+ if (!frameState) {
+ return;
+ }
+
+ // Restore it
+ nsresult rv = statefulFrame->RestoreState(frameState);
+ if (NS_FAILED(rv)) {
+ return;
+ }
+
+ // If we restore ok, remove the state from the state table
+ aState->RemoveState(stateKey);
+}
+
+void nsFrameManager::RestoreFrameState(nsIFrame* aFrame,
+ nsILayoutHistoryState* aState) {
+ MOZ_ASSERT(nullptr != aFrame && nullptr != aState,
+ "null parameters passed in");
+
+ RestoreFrameStateFor(aFrame, aState);
+
+ // Now restore state recursively for the frame hierarchy rooted at aFrame
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ RestoreFrameState(child, aState);
+ }
+ }
+}
+
+void nsFrameManager::AddSizeOfIncludingThis(nsWindowSizes& aSizes) const {
+ aSizes.mLayoutPresShellSize += aSizes.mState.mMallocSizeOf(this);
+}
diff --git a/layout/base/nsFrameManager.h b/layout/base/nsFrameManager.h
new file mode 100644
index 0000000000..fa68cb79f5
--- /dev/null
+++ b/layout/base/nsFrameManager.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/. */
+
+/* Owns the frame tree and provides APIs to manipulate it */
+
+#ifndef _nsFrameManager_h_
+#define _nsFrameManager_h_
+
+#include "nsDebug.h"
+#include "mozilla/Attributes.h"
+#include "nsFrameList.h"
+
+class nsContainerFrame;
+class nsIFrame;
+class nsILayoutHistoryState;
+class nsPlaceholderFrame;
+class nsWindowSizes;
+
+namespace mozilla {
+struct FrameDestroyContext;
+class PresShell;
+} // namespace mozilla
+
+/**
+ * Frame manager interface. The frame manager owns the frame tree model, and
+ * handles structural manipulations to it, such as appending and inserting a
+ * list of frames to a parent frame, or removing a child frame from a parent
+ * frame.
+ */
+class nsFrameManager {
+ public:
+ using DestroyContext = mozilla::FrameDestroyContext;
+
+ explicit nsFrameManager(mozilla::PresShell* aPresShell)
+ : mPresShell(aPresShell), mRootFrame(nullptr) {
+ MOZ_ASSERT(mPresShell, "need a pres shell");
+ }
+ ~nsFrameManager();
+
+ /*
+ * Gets and sets the root frame (typically the viewport). The lifetime of the
+ * root frame is controlled by the frame manager. When the frame manager is
+ * destroyed, it destroys the entire frame hierarchy.
+ */
+ nsIFrame* GetRootFrame() const { return mRootFrame; }
+ void SetRootFrame(nsIFrame* aRootFrame) {
+ NS_ASSERTION(!mRootFrame, "already have a root frame");
+ mRootFrame = aRootFrame;
+ }
+
+ /*
+ * After Destroy is called, it is an error to call any FrameManager methods.
+ * Destroy should be called when the frame tree managed by the frame
+ * manager is no longer being displayed.
+ */
+ void Destroy();
+
+ // Functions for manipulating the frame model
+ void AppendFrames(nsContainerFrame* aParentFrame,
+ mozilla::FrameChildListID aListID,
+ nsFrameList&& aFrameList);
+
+ void InsertFrames(nsContainerFrame* aParentFrame,
+ mozilla::FrameChildListID aListID, nsIFrame* aPrevFrame,
+ nsFrameList&& aFrameList);
+
+ void RemoveFrame(DestroyContext&, mozilla::FrameChildListID, nsIFrame*);
+
+ /*
+ * Capture/restore frame state for the frame subtree rooted at aFrame.
+ * aState is the document state storage object onto which each frame
+ * stores its state. Callers of CaptureFrameState are responsible for
+ * traversing next continuations of special siblings of aFrame as
+ * needed; this method will only work with actual frametree descendants
+ * of aFrame.
+ */
+
+ void CaptureFrameState(nsIFrame* aFrame, nsILayoutHistoryState* aState);
+
+ void RestoreFrameState(nsIFrame* aFrame, nsILayoutHistoryState* aState);
+
+ /*
+ * Add/restore state for one frame
+ */
+ void CaptureFrameStateFor(nsIFrame* aFrame, nsILayoutHistoryState* aState);
+
+ void RestoreFrameStateFor(nsIFrame* aFrame, nsILayoutHistoryState* aState);
+
+ void AddSizeOfIncludingThis(nsWindowSizes& aSizes) const;
+
+ protected:
+ // weak link, because the pres shell owns us
+ mozilla::PresShell* MOZ_NON_OWNING_REF mPresShell;
+ nsIFrame* mRootFrame;
+};
+
+#endif
diff --git a/layout/base/nsFrameTraversal.cpp b/layout/base/nsFrameTraversal.cpp
new file mode 100644
index 0000000000..138e8e018e
--- /dev/null
+++ b/layout/base/nsFrameTraversal.cpp
@@ -0,0 +1,305 @@
+/* -*- 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 "nsFrameTraversal.h"
+
+#include "mozilla/Assertions.h"
+#include "nsCOMPtr.h"
+#include "nsGkAtoms.h"
+
+#include "nsFrameList.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsContainerFrame.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/PopoverData.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+
+nsFrameIterator::nsFrameIterator(nsPresContext* aPresContext, nsIFrame* aStart,
+ Type aType, bool aVisual,
+ bool aLockInScrollView, bool aFollowOOFs,
+ bool aSkipPopupChecks, nsIFrame* aLimiter)
+ : mPresContext(aPresContext),
+ mLockScroll(aLockInScrollView),
+ mFollowOOFs(aFollowOOFs),
+ mSkipPopupChecks(aSkipPopupChecks),
+ mVisual(aVisual),
+ mType(aType),
+ mStart(aFollowOOFs ? nsPlaceholderFrame::GetRealFrameFor(aStart)
+ : aStart),
+ mCurrent(aStart),
+ mLast(aStart),
+ mLimiter(aLimiter),
+ mOffEdge(0) {}
+
+nsIFrame* nsFrameIterator::CurrentItem() {
+ if (mOffEdge) return nullptr;
+
+ return mCurrent;
+}
+
+bool nsFrameIterator::IsDone() { return mOffEdge != 0; }
+
+void nsFrameIterator::First() { mCurrent = mStart; }
+
+static bool IsRootFrame(nsIFrame* aFrame) { return aFrame->IsCanvasFrame(); }
+
+void nsFrameIterator::Last() {
+ nsIFrame* result;
+ nsIFrame* parent = GetCurrent();
+ // If the current frame is a popup, don't move farther up the tree.
+ // Otherwise, get the nearest root frame or popup.
+ if (mSkipPopupChecks || !parent->IsMenuPopupFrame()) {
+ while (!IsRootFrame(parent) && (result = GetParentFrameNotPopup(parent)))
+ parent = result;
+ }
+
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+
+ SetCurrent(parent);
+ if (!parent) SetOffEdge(1);
+}
+
+void nsFrameIterator::Next() {
+ // recursive-oid method to get next frame
+ nsIFrame* result = nullptr;
+ nsIFrame* parent = GetCurrent();
+ if (!parent) parent = GetLast();
+
+ if (mType == Type::Leaf) {
+ // Drill down to first leaf
+ while ((result = GetFirstChild(parent))) {
+ parent = result;
+ }
+ } else if (mType == Type::PreOrder) {
+ result = GetFirstChild(parent);
+ if (result) parent = result;
+ }
+
+ if (parent != GetCurrent()) {
+ result = parent;
+ } else {
+ while (parent) {
+ result = GetNextSibling(parent);
+ if (result) {
+ if (mType != Type::PreOrder) {
+ parent = result;
+ while ((result = GetFirstChild(parent))) {
+ parent = result;
+ }
+ result = parent;
+ }
+ break;
+ }
+ result = GetParentFrameNotPopup(parent);
+ if (!result || IsRootFrame(result) ||
+ (mLockScroll && result->IsScrollFrame())) {
+ result = nullptr;
+ break;
+ }
+ if (mType == Type::PostOrder) {
+ break;
+ }
+ parent = result;
+ }
+ }
+
+ SetCurrent(result);
+ if (!result) {
+ SetOffEdge(1);
+ SetLast(parent);
+ }
+}
+
+void nsFrameIterator::Prev() {
+ // recursive-oid method to get prev frame
+ nsIFrame* result = nullptr;
+ nsIFrame* parent = GetCurrent();
+ if (!parent) parent = GetLast();
+
+ if (mType == Type::Leaf) {
+ // Drill down to last leaf
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+ } else if (mType == Type::PostOrder) {
+ result = GetLastChild(parent);
+ if (result) parent = result;
+ }
+
+ if (parent != GetCurrent()) {
+ result = parent;
+ } else {
+ while (parent) {
+ result = GetPrevSibling(parent);
+ if (result) {
+ if (mType != Type::PostOrder) {
+ parent = result;
+ while ((result = GetLastChild(parent))) {
+ parent = result;
+ }
+ result = parent;
+ }
+ break;
+ }
+ result = GetParentFrameNotPopup(parent);
+ if (!result || IsRootFrame(result) ||
+ (mLockScroll && result->IsScrollFrame())) {
+ result = nullptr;
+ break;
+ }
+ if (mType == Type::PreOrder) {
+ break;
+ }
+ parent = result;
+ }
+ }
+
+ SetCurrent(result);
+ if (!result) {
+ SetOffEdge(-1);
+ SetLast(parent);
+ }
+}
+
+nsIFrame* nsFrameIterator::GetParentFrame(nsIFrame* aFrame) {
+ if (mFollowOOFs) aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame == mLimiter) return nullptr;
+ if (aFrame) return aFrame->GetParent();
+
+ return nullptr;
+}
+
+nsIFrame* nsFrameIterator::GetParentFrameNotPopup(nsIFrame* aFrame) {
+ if (mFollowOOFs) aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame == mLimiter) return nullptr;
+ if (aFrame) {
+ nsIFrame* parent = aFrame->GetParent();
+ if (!IsPopupFrame(parent)) return parent;
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsFrameIterator::GetFirstChild(nsIFrame* aFrame) {
+ nsIFrame* result = GetFirstChildInner(aFrame);
+ if (mLockScroll && result && result->IsScrollFrame()) return nullptr;
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+
+ if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) {
+ result = GetNextSibling(result);
+ }
+ }
+
+ return result;
+}
+
+nsIFrame* nsFrameIterator::GetLastChild(nsIFrame* aFrame) {
+ nsIFrame* result = GetLastChildInner(aFrame);
+ if (mLockScroll && result && result->IsScrollFrame()) return nullptr;
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+
+ if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) {
+ result = GetPrevSibling(result);
+ }
+ }
+
+ return result;
+}
+
+nsIFrame* nsFrameIterator::GetNextSibling(nsIFrame* aFrame) {
+ nsIFrame* result = nullptr;
+ if (mFollowOOFs) aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame == mLimiter) return nullptr;
+ if (aFrame) {
+ result = GetNextSiblingInner(aFrame);
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+ if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) {
+ result = GetNextSibling(result);
+ }
+ }
+ }
+
+ return result;
+}
+
+nsIFrame* nsFrameIterator::GetPrevSibling(nsIFrame* aFrame) {
+ nsIFrame* result = nullptr;
+ if (mFollowOOFs) aFrame = GetPlaceholderFrame(aFrame);
+ if (aFrame == mLimiter) return nullptr;
+ if (aFrame) {
+ result = GetPrevSiblingInner(aFrame);
+ if (result && mFollowOOFs) {
+ result = nsPlaceholderFrame::GetRealFrameFor(result);
+ if (IsPopupFrame(result) || IsInvokerOpenPopoverFrame(result)) {
+ result = GetPrevSibling(result);
+ }
+ }
+ }
+
+ return result;
+}
+
+nsIFrame* nsFrameIterator::GetFirstChildInner(nsIFrame* aFrame) {
+ return mVisual ? aFrame->PrincipalChildList().GetNextVisualFor(nullptr)
+ : aFrame->PrincipalChildList().FirstChild();
+}
+
+nsIFrame* nsFrameIterator::GetLastChildInner(nsIFrame* aFrame) {
+ return mVisual ? aFrame->PrincipalChildList().GetPrevVisualFor(nullptr)
+ : aFrame->PrincipalChildList().LastChild();
+}
+
+nsIFrame* nsFrameIterator::GetNextSiblingInner(nsIFrame* aFrame) {
+ if (!mVisual) {
+ return aFrame->GetNextSibling();
+ }
+ nsIFrame* parent = GetParentFrame(aFrame);
+ return parent ? parent->PrincipalChildList().GetNextVisualFor(aFrame)
+ : nullptr;
+}
+
+nsIFrame* nsFrameIterator::GetPrevSiblingInner(nsIFrame* aFrame) {
+ if (!mVisual) {
+ return aFrame->GetPrevSibling();
+ }
+ nsIFrame* parent = GetParentFrame(aFrame);
+ return parent ? parent->PrincipalChildList().GetPrevVisualFor(aFrame)
+ : nullptr;
+}
+
+nsIFrame* nsFrameIterator::GetPlaceholderFrame(nsIFrame* aFrame) {
+ if (MOZ_LIKELY(!aFrame || !aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW))) {
+ return aFrame;
+ }
+ nsIFrame* placeholder = aFrame->GetPlaceholderFrame();
+ return placeholder ? placeholder : aFrame;
+}
+
+bool nsFrameIterator::IsPopupFrame(nsIFrame* aFrame) {
+ // If skipping popup checks, pretend this isn't one.
+ if (mSkipPopupChecks) {
+ return false;
+ }
+ return aFrame && aFrame->IsMenuPopupFrame();
+}
+
+bool nsFrameIterator::IsInvokerOpenPopoverFrame(nsIFrame* aFrame) {
+ if (const nsIContent* currentContent = aFrame->GetContent()) {
+ if (const auto* popover = Element::FromNode(currentContent)) {
+ return popover && popover->IsPopoverOpen() &&
+ popover->GetPopoverData()->GetInvoker();
+ }
+ }
+ return false;
+}
diff --git a/layout/base/nsFrameTraversal.h b/layout/base/nsFrameTraversal.h
new file mode 100644
index 0000000000..2226e60345
--- /dev/null
+++ b/layout/base/nsFrameTraversal.h
@@ -0,0 +1,122 @@
+/* -*- 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 NSFRAMETRAVERSAL_H
+#define NSFRAMETRAVERSAL_H
+
+#include <cstdint>
+#include "mozilla/Attributes.h"
+
+class nsIFrame;
+class nsPresContext;
+
+class MOZ_STACK_CLASS nsFrameIterator final {
+ public:
+ void First();
+ void Next();
+ nsIFrame* CurrentItem();
+ bool IsDone();
+
+ void Last();
+ void Prev();
+
+ inline nsIFrame* Traverse(bool aForward) {
+ if (aForward) {
+ Next();
+ } else {
+ Prev();
+ }
+ return CurrentItem();
+ };
+
+ enum class Type : uint8_t {
+ // only leaf nodes
+ Leaf,
+ // "open tag" order
+ PreOrder,
+ // "close tag" order
+ PostOrder,
+ };
+ nsFrameIterator(nsPresContext* aPresContext, nsIFrame* aStart, Type aType,
+ bool aVisual, bool aLockInScrollView, bool aFollowOOFs,
+ bool aSkipPopupChecks, nsIFrame* aLimiter = nullptr);
+ ~nsFrameIterator() = default;
+
+ protected:
+ void SetCurrent(nsIFrame* aFrame) { mCurrent = aFrame; }
+ nsIFrame* GetCurrent() { return mCurrent; }
+ nsIFrame* GetStart() { return mStart; }
+ nsIFrame* GetLast() { return mLast; }
+ void SetLast(nsIFrame* aFrame) { mLast = aFrame; }
+ int8_t GetOffEdge() { return mOffEdge; }
+ void SetOffEdge(int8_t aOffEdge) { mOffEdge = aOffEdge; }
+
+ /*
+ Our own versions of the standard frame tree navigation
+ methods, which, if the iterator is following out-of-flows,
+ apply the following rules for placeholder frames:
+
+ - If a frame HAS a placeholder frame, getting its parent
+ gets the placeholder's parent.
+
+ - If a frame's first child or next/prev sibling IS a
+ placeholder frame, then we instead return the real frame.
+
+ - If a frame HAS a placeholder frame, getting its next/prev
+ sibling gets the placeholder frame's next/prev sibling.
+
+ These are all applied recursively to support multiple levels of
+ placeholders.
+ */
+
+ nsIFrame* GetParentFrame(nsIFrame* aFrame);
+ // like GetParentFrame but returns null once a popup frame is reached
+ nsIFrame* GetParentFrameNotPopup(nsIFrame* aFrame);
+
+ nsIFrame* GetFirstChild(nsIFrame* aFrame);
+ nsIFrame* GetLastChild(nsIFrame* aFrame);
+
+ nsIFrame* GetNextSibling(nsIFrame* aFrame);
+ nsIFrame* GetPrevSibling(nsIFrame* aFrame);
+
+ /*
+ These methods are overridden by the bidi visual iterator to have the
+ semantics of "get first child in visual order", "get last child in visual
+ order", "get next sibling in visual order" and "get previous sibling in
+ visual order".
+ */
+
+ nsIFrame* GetFirstChildInner(nsIFrame* aFrame);
+ nsIFrame* GetLastChildInner(nsIFrame* aFrame);
+
+ nsIFrame* GetNextSiblingInner(nsIFrame* aFrame);
+ nsIFrame* GetPrevSiblingInner(nsIFrame* aFrame);
+
+ /**
+ * Return the placeholder frame for aFrame if it has one, otherwise return
+ * aFrame itself.
+ */
+ nsIFrame* GetPlaceholderFrame(nsIFrame* aFrame);
+ bool IsPopupFrame(nsIFrame* aFrame);
+
+ bool IsInvokerOpenPopoverFrame(nsIFrame* aFrame);
+
+ nsPresContext* const mPresContext;
+ const bool mLockScroll;
+ const bool mFollowOOFs;
+ const bool mSkipPopupChecks;
+ const bool mVisual;
+ const Type mType;
+
+ private:
+ nsIFrame* const mStart;
+ nsIFrame* mCurrent;
+ nsIFrame* mLast; // the last one that was in current;
+ nsIFrame* mLimiter;
+ int8_t mOffEdge; // 0= no -1 to far prev, 1 to far next;
+};
+
+#endif // NSFRAMETRAVERSAL_H
diff --git a/layout/base/nsGenConList.cpp b/layout/base/nsGenConList.cpp
new file mode 100644
index 0000000000..b770ee539a
--- /dev/null
+++ b/layout/base/nsGenConList.cpp
@@ -0,0 +1,229 @@
+/* -*- 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 nsCounterList and nsQuoteList */
+
+#include "nsGenConList.h"
+#include "nsContentUtils.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+
+void nsGenConNode::CheckFrameAssertions() {
+ NS_ASSERTION(
+ mContentIndex < int32_t(mPseudoFrame->StyleContent()->ContentCount()) ||
+ // Special-case for the USE node created for the legacy markers,
+ // which don't use the content property.
+ mContentIndex == 0,
+ "index out of range");
+ // We allow negative values of mContentIndex for 'counter-reset' and
+ // 'counter-increment'.
+
+ NS_ASSERTION(mContentIndex < 0 ||
+ mPseudoFrame->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::before ||
+ mPseudoFrame->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::after ||
+ mPseudoFrame->Style()->GetPseudoType() ==
+ mozilla::PseudoStyleType::marker,
+ "not CSS generated content and not counter change");
+ NS_ASSERTION(mContentIndex < 0 ||
+ mPseudoFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT),
+ "not generated content and not counter change");
+}
+
+void nsGenConList::Clear() {
+ // Delete entire list.
+ mNodes.Clear();
+ while (nsGenConNode* node = mList.popFirst()) {
+ delete node;
+ }
+ mSize = 0;
+ mLastInserted = nullptr;
+}
+
+bool nsGenConList::DestroyNodesFor(nsIFrame* aFrame) {
+ // This algorithm relies on the invariant that nodes of a frame are
+ // put contiguously in the linked list. This is guaranteed because
+ // each frame is mapped to only one (nsIContent, pseudoType) pair,
+ // and the nodes in the linked list are put in the tree order based
+ // on that pair and offset inside frame.
+ nsGenConNode* node = mNodes.Extract(aFrame).valueOr(nullptr);
+ if (!node) {
+ return false;
+ }
+ MOZ_ASSERT(node->mPseudoFrame == aFrame);
+
+ while (node && node->mPseudoFrame == aFrame) {
+ nsGenConNode* nextNode = Next(node);
+ Destroy(node);
+ node = nextNode;
+ }
+
+ // Modification of the list invalidates the cached pointer.
+ mLastInserted = nullptr;
+
+ return true;
+}
+
+/**
+ * Compute the type of the pseudo and the content for the pseudo that
+ * we'll use for comparison purposes.
+ * @param aContent the content to use is stored here; it's the element
+ * that generated the pseudo, or (if not for generated content), the frame's
+ * own element
+ * @return -2 for ::marker, -1 for ::before, +1 for ::after, and 0 otherwise.
+ */
+inline int32_t PseudoCompareType(nsIFrame* aFrame, nsIContent** aContent) {
+ auto pseudo = aFrame->Style()->GetPseudoType();
+ if (pseudo == mozilla::PseudoStyleType::marker) {
+ *aContent = aFrame->GetContent()->GetParent();
+ return -2;
+ }
+ if (pseudo == mozilla::PseudoStyleType::before) {
+ *aContent = aFrame->GetContent()->GetParent();
+ return -1;
+ }
+ if (pseudo == mozilla::PseudoStyleType::after) {
+ *aContent = aFrame->GetContent()->GetParent();
+ return 1;
+ }
+ *aContent = aFrame->GetContent();
+ return 0;
+}
+
+/* static */
+bool nsGenConList::NodeAfter(const nsGenConNode* aNode1,
+ const nsGenConNode* aNode2) {
+ nsIFrame* frame1 = aNode1->mPseudoFrame;
+ nsIFrame* frame2 = aNode2->mPseudoFrame;
+ if (frame1 == frame2) {
+ NS_ASSERTION(aNode2->mContentIndex != aNode1->mContentIndex, "identical");
+ return aNode1->mContentIndex > aNode2->mContentIndex;
+ }
+ nsIContent* content1;
+ nsIContent* content2;
+ int32_t pseudoType1 = PseudoCompareType(frame1, &content1);
+ int32_t pseudoType2 = PseudoCompareType(frame2, &content2);
+ if (content1 == content2) {
+ NS_ASSERTION(pseudoType1 != pseudoType2, "identical");
+ if (pseudoType1 == 0 || pseudoType2 == 0) {
+ return pseudoType2 == 0;
+ }
+ return pseudoType1 > pseudoType2;
+ }
+
+ // Two pseudo-elements of different elements, we want to treat them as if
+ // they were normal elements and just use tree order.
+ content1 = frame1->GetContent();
+ content2 = frame2->GetContent();
+
+ int32_t cmp = nsContentUtils::CompareTreePosition<TreeKind::Flat>(
+ content1, content2, /* aCommonAncestor = */ nullptr);
+ MOZ_ASSERT(cmp != 0, "same content, different frames");
+ return cmp > 0;
+}
+
+nsGenConNode* nsGenConList::BinarySearch(
+ const mozilla::FunctionRef<bool(nsGenConNode*)>& aIsAfter) {
+ if (mList.isEmpty()) {
+ return nullptr;
+ }
+
+ // The range of indices at which |aNode| could end up.
+ // (We already know it can't be at index mSize.)
+ uint32_t first = 0, last = mSize - 1;
+
+ // A cursor to avoid walking more than the length of the list.
+ nsGenConNode* curNode = mList.getLast();
+ uint32_t curIndex = mSize - 1;
+
+ while (first != last) {
+ uint32_t test = first + (last - first) / 2;
+ if (last == curIndex) {
+ for (; curIndex != test; --curIndex) curNode = Prev(curNode);
+ } else {
+ for (; curIndex != test; ++curIndex) curNode = Next(curNode);
+ }
+
+ if (aIsAfter(curNode)) {
+ first = test + 1;
+ // if we exit the loop, we need curNode to be right
+ ++curIndex;
+ curNode = Next(curNode);
+ } else {
+ last = test;
+ }
+ }
+
+ return curNode;
+}
+
+void nsGenConList::Insert(nsGenConNode* aNode) {
+ // Check for append.
+ if (mList.isEmpty() || NodeAfter(aNode, mList.getLast())) {
+ mList.insertBack(aNode);
+ } else if (mLastInserted && mLastInserted != mList.getLast() &&
+ NodeAfter(aNode, mLastInserted) &&
+ NodeAfter(Next(mLastInserted), aNode)) {
+ // Fast path for inserting many consecutive nodes in one place
+ mLastInserted->setNext(aNode);
+ } else {
+ auto IsAfter = [aNode](nsGenConNode* curNode) {
+ return NodeAfter(aNode, curNode);
+ };
+ auto* insertionNode = BinarySearch(IsAfter);
+ insertionNode->setPrevious(aNode);
+ }
+ ++mSize;
+
+ mLastInserted = aNode;
+
+ // Set the mapping only if it is the first node of the frame.
+ // The DEBUG blocks below are for ensuring the invariant required by
+ // nsGenConList::DestroyNodesFor. See comment there.
+ if (IsFirst(aNode) || Prev(aNode)->mPseudoFrame != aNode->mPseudoFrame) {
+#ifdef DEBUG
+ if (nsGenConNode* oldFrameFirstNode = mNodes.Get(aNode->mPseudoFrame)) {
+ MOZ_ASSERT(Next(aNode) == oldFrameFirstNode,
+ "oldFrameFirstNode should now be immediately after "
+ "the newly-inserted one.");
+ } else {
+ // If the node is not the only node in the list.
+ if (!IsFirst(aNode) || !IsLast(aNode)) {
+ nsGenConNode* nextNode = Next(aNode);
+ MOZ_ASSERT(!nextNode || nextNode->mPseudoFrame != aNode->mPseudoFrame,
+ "There shouldn't exist any node for this frame.");
+ // If the node is neither the first nor the last node
+ if (!IsFirst(aNode) && !IsLast(aNode)) {
+ MOZ_ASSERT(Prev(aNode)->mPseudoFrame != nextNode->mPseudoFrame,
+ "New node should not break contiguity of nodes of "
+ "the same frame.");
+ }
+ }
+ }
+#endif
+ mNodes.InsertOrUpdate(aNode->mPseudoFrame, aNode);
+ } else {
+#ifdef DEBUG
+ nsGenConNode* frameFirstNode = mNodes.Get(aNode->mPseudoFrame);
+ MOZ_ASSERT(frameFirstNode, "There should exist node map for the frame.");
+ for (nsGenConNode* curNode = Prev(aNode); curNode != frameFirstNode;
+ curNode = Prev(curNode)) {
+ MOZ_ASSERT(curNode->mPseudoFrame == aNode->mPseudoFrame,
+ "Every node between frameFirstNode and the new node inserted "
+ "should refer to the same frame.");
+ MOZ_ASSERT(!IsFirst(curNode),
+ "The newly-inserted node should be in a contiguous run after "
+ "frameFirstNode, thus frameFirstNode should be reached before "
+ "the first node of mList.");
+ }
+#endif
+ }
+
+ NS_ASSERTION(IsFirst(aNode) || NodeAfter(aNode, Prev(aNode)),
+ "sorting error");
+ NS_ASSERTION(IsLast(aNode) || NodeAfter(Next(aNode), aNode), "sorting error");
+}
diff --git a/layout/base/nsGenConList.h b/layout/base/nsGenConList.h
new file mode 100644
index 0000000000..271c426080
--- /dev/null
+++ b/layout/base/nsGenConList.h
@@ -0,0 +1,132 @@
+/* -*- 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 nsCounterList and nsQuoteList */
+
+#ifndef nsGenConList_h___
+#define nsGenConList_h___
+
+#include "mozilla/FunctionRef.h"
+#include "mozilla/LinkedList.h"
+#include "nsStyleStruct.h"
+#include "nsCSSPseudoElements.h"
+#include "nsTextNode.h"
+#include <functional>
+
+class nsGenConList;
+class nsIFrame;
+
+struct nsGenConNode : public mozilla::LinkedListElement<nsGenConNode> {
+ using StyleContentType = mozilla::StyleContentItem::Tag;
+
+ // The wrapper frame for all of the pseudo-element's content. This
+ // frame generally has useful style data and has the
+ // NS_FRAME_GENERATED_CONTENT bit set (so we use it to track removal),
+ // but does not necessarily for |nsCounterChangeNode|s.
+ nsIFrame* mPseudoFrame;
+
+ // Index within the list of things specified by the 'content' property,
+ // which is needed to do 'content: open-quote open-quote' correctly,
+ // and needed for similar cases for counters.
+ const int32_t mContentIndex;
+
+ // null for:
+ // * content: no-open-quote / content: no-close-quote
+ // * counter nodes for increments and resets
+ RefPtr<nsTextNode> mText;
+
+ explicit nsGenConNode(int32_t aContentIndex)
+ : mPseudoFrame(nullptr), mContentIndex(aContentIndex) {}
+
+ /**
+ * Finish initializing the generated content node once we know the
+ * relevant text frame. This must be called just after
+ * the textframe has been initialized. This need not be called at all
+ * for nodes that don't generate text. This will generally set the
+ * mPseudoFrame, insert the node into aList, and set aTextFrame up
+ * with the correct text.
+ * @param aList the list the node belongs to
+ * @param aPseudoFrame the :before or :after frame
+ * @param aTextFrame the textframe where the node contents will render
+ * @return true iff this marked the list dirty
+ */
+ virtual bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame) {
+ mPseudoFrame = aPseudoFrame;
+ CheckFrameAssertions();
+ return false;
+ }
+
+ virtual ~nsGenConNode() = default; // XXX Avoid, perhaps?
+
+ protected:
+ void CheckFrameAssertions();
+};
+
+class nsGenConList {
+ protected:
+ mozilla::LinkedList<nsGenConNode> mList;
+ uint32_t mSize;
+
+ public:
+ nsGenConList() : mSize(0), mLastInserted(nullptr) {}
+ ~nsGenConList() { Clear(); }
+ void Clear();
+ static nsGenConNode* Next(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode->getNext();
+ }
+ static nsGenConNode* Prev(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode->getPrevious();
+ }
+ void Insert(nsGenConNode* aNode);
+
+ // Destroy all nodes with aFrame as parent. Returns true if some nodes
+ // have been destroyed; otherwise false.
+ bool DestroyNodesFor(nsIFrame* aFrame);
+
+ // Return the first node for aFrame on this list, or nullptr.
+ nsGenConNode* GetFirstNodeFor(nsIFrame* aFrame) const {
+ return mNodes.Get(aFrame);
+ }
+
+ // Return true if |aNode1| is after |aNode2|.
+ static bool NodeAfter(const nsGenConNode* aNode1, const nsGenConNode* aNode2);
+
+ // Find the first element in the list for which the given comparator returns
+ // true. This does a binary search on the list contents.
+ nsGenConNode* BinarySearch(
+ const mozilla::FunctionRef<bool(nsGenConNode*)>& aIsAfter);
+
+ nsGenConNode* GetLast() { return mList.getLast(); }
+
+ bool IsFirst(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode == mList.getFirst();
+ }
+
+ bool IsLast(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ return aNode == mList.getLast();
+ }
+
+ private:
+ void Destroy(nsGenConNode* aNode) {
+ MOZ_ASSERT(aNode, "aNode cannot be nullptr!");
+ delete aNode;
+ mSize--;
+ }
+
+ // Map from frame to the first nsGenConNode of it in the list.
+ nsTHashMap<nsPtrHashKey<nsIFrame>, nsGenConNode*> mNodes;
+
+ // A weak pointer to the node most recently inserted, used to avoid repeated
+ // list traversals in Insert().
+ nsGenConNode* mLastInserted;
+};
+
+#endif /* nsGenConList_h___ */
diff --git a/layout/base/nsIDocumentViewerPrint.h b/layout/base/nsIDocumentViewerPrint.h
new file mode 100644
index 0000000000..b08b409b36
--- /dev/null
+++ b/layout/base/nsIDocumentViewerPrint.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/. */
+
+#ifndef nsIDocumentViewerPrint_h___
+#define nsIDocumentViewerPrint_h___
+
+#include "nsISupports.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+class PresShell;
+class ServoStyleSet;
+} // namespace mozilla
+class nsPresContext;
+class nsViewManager;
+
+// {c6f255cf-cadd-4382-b57f-cd2a9874169b}
+#define NS_IDOCUMENT_VIEWER_PRINT_IID \
+ { \
+ 0xc6f255cf, 0xcadd, 0x4382, { \
+ 0xb5, 0x7f, 0xcd, 0x2a, 0x98, 0x74, 0x16, 0x9b \
+ } \
+ }
+
+/**
+ * A DocumentViewerPrint is an INTERNAL Interface used for interaction
+ * between the DocumentViewer and nsPrintJob.
+ */
+class nsIDocumentViewerPrint : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDOCUMENT_VIEWER_PRINT_IID)
+
+ virtual bool GetIsPrinting() const = 0;
+
+ virtual void SetIsPrintPreview(bool aIsPrintPreview) = 0;
+ virtual bool GetIsPrintPreview() const = 0;
+
+ /**
+ * This is used by nsPagePrintTimer to make nsDocumentViewer::Destroy()
+ * a no-op until printing is finished. That prevents the nsDocumentViewer
+ * and its document, presshell and prescontext from going away.
+ */
+ virtual void IncrementDestroyBlockedCount() = 0;
+ virtual void DecrementDestroyBlockedCount() = 0;
+
+ virtual void OnDonePrinting() = 0;
+
+ /**
+ * Replaces the current presentation with print preview presentation.
+ */
+ virtual void SetPrintPreviewPresentation(nsViewManager* aViewManager,
+ nsPresContext* aPresContext,
+ mozilla::PresShell* aPresShell) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIDocumentViewerPrint,
+ NS_IDOCUMENT_VIEWER_PRINT_IID)
+
+/* Use this macro when declaring classes that implement this interface. */
+#define NS_DECL_NSIDOCUMENTVIEWERPRINT \
+ bool GetIsPrinting() const override; \
+ void SetIsPrintPreview(bool aIsPrintPreview) override; \
+ bool GetIsPrintPreview() const override; \
+ void IncrementDestroyBlockedCount() override; \
+ void DecrementDestroyBlockedCount() override; \
+ void OnDonePrinting() override; \
+ void SetPrintPreviewPresentation(nsViewManager* aViewManager, \
+ nsPresContext* aPresContext, \
+ mozilla::PresShell* aPresShell) override;
+
+#endif /* nsIDocumentViewerPrint_h___ */
diff --git a/layout/base/nsILayoutHistoryState.idl b/layout/base/nsILayoutHistoryState.idl
new file mode 100644
index 0000000000..5f6f587bc5
--- /dev/null
+++ b/layout/base/nsILayoutHistoryState.idl
@@ -0,0 +1,120 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 container for information saved in session history when
+ * the document is not
+ */
+
+#include "nsISupports.idl"
+
+
+[ptr] native PresStatePtr(mozilla::PresState);
+native PresStateUnique(mozilla::UniquePtr<mozilla::PresState>);
+native PresState(mozilla::PresState);
+[ref] native nsCString(const nsCString);
+native constBool(const bool);
+
+%{C++
+#include "nsStringFwd.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+class PresState;
+} // namespace mozilla
+
+template<typename> struct already_AddRefed;
+%}
+
+[scriptable, builtinclass, uuid(aef27cb3-4df9-4eeb-b0b0-ac56cf861d04)]
+interface nsILayoutHistoryState : nsISupports
+{
+ /**
+ * Whether this LayoutHistoryState contains any PresStates.
+ */
+ readonly attribute boolean hasStates;
+
+ /**
+ * Get the keys of all PresStates held by this LayoutHistoryState.
+ * Note: Check hasStates first.
+ */
+ Array<ACString> getKeys();
+
+ /*
+ * Attempts to get the data of the PresState corresponding to
+ * the passed key. Throws if no data could be found.
+ */
+ void getPresState(in ACString aKey,
+ out float aScrollX, out float aScrollY,
+ out boolean aAllowScrollOriginDowngrade,
+ out float aRes);
+
+ /**
+ * Constructs a new PresState object based on the supplied data
+ * and adds it to the LayoutHistoryState.
+ */
+ void addNewPresState(in ACString aKey,
+ in float aScrollX, in float aScrollY,
+ in boolean aAllowScrollOriginDowngrade,
+ in float aRes);
+
+ // Native only interface, converted from the original nsILayoutHistoryState.h
+
+ /**
+ * Set |aState| as the state object for |aKey|.
+ * This _transfers_ownership_ of |aState| to the LayoutHistoryState.
+ * It will be freed when RemoveState() is called or when the
+ * LayoutHistoryState is destroyed.
+ */
+ [noscript, notxpcom, nostdcall] void AddState(in nsCString aKey, in PresStateUnique aState);
+
+ /**
+ * Look up the state object for |aKey|.
+ */
+ [noscript, notxpcom, nostdcall] PresStatePtr GetState(in nsCString aKey);
+
+ /**
+ * Remove the state object for |aKey|.
+ */
+ [noscript, notxpcom, nostdcall] void RemoveState(in nsCString aKey);
+
+ /**
+ * Check whether this history has any states in it
+ */
+ [noscript, notxpcom, nostdcall] boolean HasStates();
+
+ /**
+ * Sets whether this history can contain only scroll position history
+ * or all possible history
+ */
+ [noscript, notxpcom, nostdcall] void SetScrollPositionOnly(in constBool aFlag);
+
+ /**
+ * Resets PresState::GetScrollState of all PresState objects to 0,0.
+ */
+ [noscript, notxpcom, nostdcall] void ResetScrollState();
+
+ /**
+ * Get the contents of the layout history.
+ */
+ [noscript, notxpcom, nostdcall] void GetContents(out boolean aScrollPositionOnly,
+ out Array<ACString> aKeys,
+ out Array<PresState> aStates);
+
+ /**
+ * Remove all the states and clear the scroll position only flag.
+ */
+ [noscript, notxpcom, nostdcall] void Reset();
+};
+
+%{C++
+/* Defined in nsLayoutHistoryState.cpp */
+already_AddRefed<nsILayoutHistoryState>
+NS_NewLayoutHistoryState();
+
+namespace mozilla {
+mozilla::UniquePtr<mozilla::PresState> NewPresState();
+} // namespace mozilla
+%}
diff --git a/layout/base/nsIPercentBSizeObserver.h b/layout/base/nsIPercentBSizeObserver.h
new file mode 100644
index 0000000000..f11e1e2774
--- /dev/null
+++ b/layout/base/nsIPercentBSizeObserver.h
@@ -0,0 +1,33 @@
+/* -*- 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 nsIPercentBSizeObserver_h___
+#define nsIPercentBSizeObserver_h___
+
+#include "nsQueryFrame.h"
+
+namespace mozilla {
+struct ReflowInput;
+} // namespace mozilla
+
+/**
+ * This interface is supported by frames that need to provide computed bsize
+ * values to children during reflow which would otherwise not happen. Currently
+ * only table cells support this.
+ */
+class nsIPercentBSizeObserver {
+ public:
+ NS_DECL_QUERYFRAME_TARGET(nsIPercentBSizeObserver)
+
+ // Notify the observer that aReflowInput has no computed bsize,
+ // but it has a percent bsize
+ virtual void NotifyPercentBSize(const mozilla::ReflowInput& aReflowInput) = 0;
+
+ // Ask the observer if it should observe aReflowInput.frame
+ virtual bool NeedsToObserve(const mozilla::ReflowInput& aReflowInput) = 0;
+};
+
+#endif // nsIPercentBSizeObserver_h___
diff --git a/layout/base/nsIPreloadedStyleSheet.idl b/layout/base/nsIPreloadedStyleSheet.idl
new file mode 100644
index 0000000000..ad617ae4e3
--- /dev/null
+++ b/layout/base/nsIPreloadedStyleSheet.idl
@@ -0,0 +1,16 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 "nsISupports.idl"
+
+/**
+ * The nsIPreloadedStyleSheet interface is an opaque interface for
+ * style sheets returned by nsIStyleSheetService.preloadSheet, and
+ * which can be passed to nsIDOMWindowUtils.addSheet.
+ */
+[scriptable, builtinclass, uuid(2e2a84d0-2102-4b9e-9b78-1670623a582d)]
+interface nsIPreloadedStyleSheet : nsISupports
+{
+};
diff --git a/layout/base/nsIReflowCallback.h b/layout/base/nsIReflowCallback.h
new file mode 100644
index 0000000000..aa7dbd3c24
--- /dev/null
+++ b/layout/base/nsIReflowCallback.h
@@ -0,0 +1,34 @@
+/* -*- 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 nsIReflowCallback_h___
+#define nsIReflowCallback_h___
+
+/**
+ * Reflow callback interface.
+ * These are not refcounted. Objects must be removed from the presshell
+ * callback list before they die.
+ * Protocol: objects will either get a ReflowFinished() call when a reflow
+ * has finished or a ReflowCallbackCanceled() call if the shell is destroyed,
+ * whichever happens first. If the object is explicitly removed from the shell
+ * (using PresShell::CancelReflowCallback()) before that occurs then neither
+ * of the callback methods are called.
+ */
+class nsIReflowCallback {
+ public:
+ /**
+ * The presshell calls this when reflow has finished. Return true if
+ * you need a FlushType::Layout to happen after this.
+ */
+ virtual bool ReflowFinished() = 0;
+ /**
+ * The presshell calls this on outstanding callback requests in its
+ * Destroy() method. The shell removes the request after calling
+ * ReflowCallbackCanceled().
+ */
+ virtual void ReflowCallbackCanceled() = 0;
+};
+
+#endif /* nsIReflowCallback_h___ */
diff --git a/layout/base/nsIStyleSheetService.idl b/layout/base/nsIStyleSheetService.idl
new file mode 100644
index 0000000000..93d6795fe2
--- /dev/null
+++ b/layout/base/nsIStyleSheetService.idl
@@ -0,0 +1,71 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 managing user and user-agent style sheets */
+
+#include "nsISupports.idl"
+
+interface nsIPreloadedStyleSheet;
+interface nsIURI;
+
+/*
+ * nsIStyleSheetService allows extensions or embeddors to add to the
+ * built-in list of user or agent style sheets.
+ */
+
+[scriptable, uuid(4de68896-e8eb-41de-8237-a797b570ac4a)]
+interface nsIStyleSheetService : nsISupports
+{
+ const unsigned long AGENT_SHEET = 0;
+ const unsigned long USER_SHEET = 1;
+ const unsigned long AUTHOR_SHEET = 2;
+
+ /**
+ * Synchronously loads a style sheet from |sheetURI| and adds it to the list
+ * of user or agent style sheets.
+ *
+ * A user sheet loaded via this API will come before userContent.css and
+ * userChrome.css in the cascade (so the rules in it will have lower
+ * precedence than rules in those sheets).
+ *
+ * An agent sheet loaded via this API will come after ua.css in the cascade
+ * (so the rules in it will have higher precedence than rules in ua.css).
+ *
+ * The relative ordering of two user or two agent sheets loaded via
+ * this API is undefined.
+ *
+ * Sheets added via this API take effect on all documents, including
+ * already-loaded ones, immediately.
+ */
+ void loadAndRegisterSheet(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Returns true if a style sheet at |sheetURI| has previously been
+ * added to the list of style sheets specified by |type|.
+ */
+ boolean sheetRegistered(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Synchronously loads a style sheet from |sheetURI| and returns the
+ * new style sheet object. Can be used with nsIDOMWindowUtils.addSheet.
+ */
+ nsIPreloadedStyleSheet preloadSheet(in nsIURI sheetURI,
+ in unsigned long type);
+
+ /**
+ * Asynchronously loads a style sheet from |sheetURI| and returns a Promise
+ * which resolves to the new style sheet object, which can be used with
+ * nsIDOMWindowUtils.addSheet, when it has completed loading.
+ */
+ [implicit_jscontext]
+ jsval preloadSheetAsync(in nsIURI sheetURI, in unsigned long type);
+
+ /**
+ * Remove the style sheet at |sheetURI| from the list of style sheets
+ * specified by |type|. The removal takes effect immediately, even for
+ * already-loaded documents.
+ */
+ void unregisterSheet(in nsIURI sheetURI, in unsigned long type);
+};
diff --git a/layout/base/nsLayoutDebugger.cpp b/layout/base/nsLayoutDebugger.cpp
new file mode 100644
index 0000000000..1cca582ec5
--- /dev/null
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -0,0 +1,276 @@
+/* -*- 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/. */
+
+/* some layout debugging functions that ought to live in nsFrame.cpp */
+
+#include "nsAttrValue.h"
+#include "nsIFrame.h"
+#include "nsDisplayList.h"
+#include "nsPrintfCString.h"
+
+#include <stdio.h>
+
+using namespace mozilla;
+using namespace mozilla::layers;
+
+static std::ostream& operator<<(std::ostream& os, const nsPrintfCString& rhs) {
+ os << rhs.get();
+ return os;
+}
+
+static void PrintDisplayListTo(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ std::stringstream& aStream, uint32_t aIndent,
+ bool aDumpHtml);
+
+static void PrintDisplayItemTo(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem, std::stringstream& aStream,
+ uint32_t aIndent, bool aDumpSublist,
+ bool aDumpHtml) {
+ std::stringstream ss;
+
+ if (!aDumpHtml) {
+ for (uint32_t indent = 0; indent < aIndent; indent++) {
+ aStream << " ";
+ }
+ }
+ nsAutoString contentData;
+ nsIFrame* f = aItem->Frame();
+#ifdef DEBUG_FRAME_DUMP
+ f->GetFrameName(contentData);
+#endif
+ nsIContent* content = f->GetContent();
+ if (content) {
+ nsString tmp;
+ if (content->GetID()) {
+ content->GetID()->ToString(tmp);
+ contentData.AppendLiteral(" id:");
+ contentData.Append(tmp);
+ }
+ const nsAttrValue* classes =
+ content->IsElement() ? content->AsElement()->GetClasses() : nullptr;
+ if (classes) {
+ classes->ToString(tmp);
+ contentData.AppendLiteral(" class:");
+ contentData.Append(tmp);
+ }
+ }
+ bool snap;
+ nsRect rect = aBuilder ? aItem->GetBounds(aBuilder, &snap) : nsRect();
+ nsRect component =
+ aBuilder ? aItem->GetComponentAlphaBounds(aBuilder) : nsRect();
+ nsDisplayList* list = aItem->GetChildren();
+ const DisplayItemClip& clip = aItem->GetClip();
+ nsRegion opaque =
+ aBuilder ? aItem->GetOpaqueRegion(aBuilder, &snap) : nsRect();
+
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ aStream << nsPrintfCString("<a href=\"javascript:ViewImage('%s')\">",
+ string.BeginReading());
+ }
+#endif
+
+ aStream << nsPrintfCString(
+ "%s p=0x%p f=0x%p(%s) key=%d %sbounds(%d,%d,%d,%d) "
+ "componentAlpha(%d,%d,%d,%d) clip(%s) asr(%s) clipChain(%s)%s ",
+ aItem->Name(), aItem, (void*)f, NS_ConvertUTF16toUTF8(contentData).get(),
+ aItem->GetPerFrameKey(),
+ (aItem->ZIndex() ? nsPrintfCString("z=%d ", aItem->ZIndex()).get() : ""),
+ rect.x, rect.y, rect.width, rect.height, component.x, component.y,
+ component.width, component.height, clip.ToString().get(),
+ ActiveScrolledRoot::ToString(aItem->GetActiveScrolledRoot()).get(),
+ DisplayItemClipChain::ToString(aItem->GetClipChain()).get(),
+ (aBuilder && aItem->IsUniform(aBuilder)) ? " uniform" : "");
+
+ for (auto iter = opaque.RectIter(); !iter.Done(); iter.Next()) {
+ const nsRect& r = iter.Get();
+ aStream << nsPrintfCString(" (opaque %d,%d,%d,%d)", r.x, r.y, r.width,
+ r.height);
+ }
+
+ const auto& willChange = aItem->Frame()->StyleDisplay()->mWillChange;
+ if (!willChange.features.IsEmpty()) {
+ aStream << " (will-change=";
+ for (size_t i = 0; i < willChange.features.Length(); i++) {
+ if (i > 0) {
+ aStream << ",";
+ }
+ nsDependentAtomString buffer(willChange.features.AsSpan()[i].AsAtom());
+ aStream << NS_LossyConvertUTF16toASCII(buffer).get();
+ }
+ aStream << ")";
+ }
+
+ if (aItem->HasHitTestInfo()) {
+ const auto& hitTestInfo = aItem->GetHitTestInfo();
+ aStream << nsPrintfCString(" hitTestInfo(0x%x)",
+ hitTestInfo.Info().serialize());
+
+ nsRect area = hitTestInfo.Area();
+ aStream << nsPrintfCString(" hitTestArea(%d,%d,%d,%d)", area.x, area.y,
+ area.width, area.height);
+ }
+
+ auto ReuseStateToString = [](nsDisplayItem::ReuseState aState) {
+ switch (aState) {
+ case nsDisplayItem::ReuseState::None:
+ return "None";
+ case nsDisplayItem::ReuseState::Reusable:
+ return "Reusable";
+ case nsDisplayItem::ReuseState::PreProcessed:
+ return "PreProcessed";
+ case nsDisplayItem::ReuseState::Reused:
+ return "Reused";
+ }
+
+ MOZ_ASSERT_UNREACHABLE();
+ return "";
+ };
+
+ aStream << nsPrintfCString(" reuse-state(%s)",
+ ReuseStateToString(aItem->GetReuseState()));
+
+ // Display item specific debug info
+ aItem->WriteDebugInfo(aStream);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ aStream << "</a>";
+ }
+#endif
+#ifdef MOZ_DUMP_PAINTING
+ if (aItem->GetType() == DisplayItemType::TYPE_MASK) {
+ nsCString str;
+ (static_cast<nsDisplayMasksAndClipPaths*>(aItem))->PrintEffects(str);
+ aStream << str.get();
+ }
+
+ if (aItem->GetType() == DisplayItemType::TYPE_FILTER) {
+ nsCString str;
+ (static_cast<nsDisplayFilters*>(aItem))->PrintEffects(str);
+ aStream << str.get();
+ }
+#endif
+ aStream << "\n";
+#ifdef MOZ_DUMP_PAINTING
+ if (aDumpHtml && aItem->Painted()) {
+ nsCString string(aItem->Name());
+ string.Append('-');
+ string.AppendInt((uint64_t)aItem);
+ aStream << nsPrintfCString("<br><img id=\"%s\">\n", string.BeginReading());
+ }
+#endif
+
+ if (aDumpSublist && list) {
+ PrintDisplayListTo(aBuilder, *list, aStream, aIndent + 1, aDumpHtml);
+ }
+}
+
+static void PrintDisplayListTo(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ std::stringstream& aStream, uint32_t aIndent,
+ bool aDumpHtml) {
+ if (aDumpHtml) {
+ aStream << "<ul>";
+ }
+
+ for (nsDisplayItem* i : aList) {
+ if (aDumpHtml) {
+ aStream << "<li>";
+ }
+ PrintDisplayItemTo(aBuilder, i, aStream, aIndent, true, aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</li>";
+ }
+ }
+
+ if (aDumpHtml) {
+ aStream << "</ul>";
+ }
+}
+
+void nsIFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList, bool aDumpHtml) {
+ std::stringstream ss;
+ PrintDisplayList(aBuilder, aList, ss, aDumpHtml);
+ fprintf_stderr(stderr, "%s", ss.str().c_str());
+}
+
+void nsIFrame::PrintDisplayList(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList,
+ std::stringstream& aStream, bool aDumpHtml) {
+ PrintDisplayListTo(aBuilder, aList, aStream, 0, aDumpHtml);
+}
+
+void nsIFrame::PrintDisplayItem(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem,
+ std::stringstream& aStream, uint32_t aIndent,
+ bool aDumpSublist, bool aDumpHtml) {
+ PrintDisplayItemTo(aBuilder, aItem, aStream, aIndent, aDumpSublist,
+ aDumpHtml);
+}
+
+/**
+ * The two functions below are intended to be called from a debugger.
+ */
+void PrintDisplayItemToStdout(nsDisplayListBuilder* aBuilder,
+ nsDisplayItem* aItem) {
+ std::stringstream stream;
+ PrintDisplayItemTo(aBuilder, aItem, stream, 0, true, false);
+ puts(stream.str().c_str());
+}
+
+void PrintDisplayListToStdout(nsDisplayListBuilder* aBuilder,
+ const nsDisplayList& aList) {
+ std::stringstream stream;
+ PrintDisplayListTo(aBuilder, aList, stream, 0, false);
+ puts(stream.str().c_str());
+}
+
+#ifdef MOZ_DUMP_PAINTING
+static void PrintDisplayListSetItem(nsDisplayListBuilder* aBuilder,
+ const char* aItemName,
+ const nsDisplayList& aList,
+ std::stringstream& aStream,
+ bool aDumpHtml) {
+ if (aDumpHtml) {
+ aStream << "<li>";
+ }
+ aStream << aItemName << "\n";
+ PrintDisplayListTo(aBuilder, aList, aStream, 0, aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</li>";
+ }
+}
+
+void nsIFrame::PrintDisplayListSet(nsDisplayListBuilder* aBuilder,
+ const nsDisplayListSet& aSet,
+ std::stringstream& aStream, bool aDumpHtml) {
+ if (aDumpHtml) {
+ aStream << "<ul>";
+ }
+ PrintDisplayListSetItem(aBuilder, "[BorderBackground]",
+ *(aSet.BorderBackground()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[BlockBorderBackgrounds]",
+ *(aSet.BlockBorderBackgrounds()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Floats]", *(aSet.Floats()), aStream,
+ aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[PositionedDescendants]",
+ *(aSet.PositionedDescendants()), aStream, aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Outlines]", *(aSet.Outlines()), aStream,
+ aDumpHtml);
+ PrintDisplayListSetItem(aBuilder, "[Content]", *(aSet.Content()), aStream,
+ aDumpHtml);
+ if (aDumpHtml) {
+ aStream << "</ul>";
+ }
+}
+
+#endif
diff --git a/layout/base/nsLayoutHistoryState.cpp b/layout/base/nsLayoutHistoryState.cpp
new file mode 100644
index 0000000000..f79545fb3b
--- /dev/null
+++ b/layout/base/nsLayoutHistoryState.cpp
@@ -0,0 +1,162 @@
+/* -*- 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/. */
+
+/*
+ * container for information saved in session history when the document
+ * is not
+ */
+
+#include "nsILayoutHistoryState.h"
+#include "nsWeakReference.h"
+#include "mozilla/PresState.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsTHashMap.h"
+
+using namespace mozilla;
+
+class nsLayoutHistoryState final : public nsILayoutHistoryState,
+ public nsSupportsWeakReference {
+ public:
+ nsLayoutHistoryState() : mScrollPositionOnly(false) {}
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSILAYOUTHISTORYSTATE
+
+ private:
+ ~nsLayoutHistoryState() = default;
+ bool mScrollPositionOnly;
+
+ nsTHashMap<nsCString, UniquePtr<PresState>> mStates;
+};
+
+already_AddRefed<nsILayoutHistoryState> NS_NewLayoutHistoryState() {
+ RefPtr<nsLayoutHistoryState> state = new nsLayoutHistoryState();
+ return state.forget();
+}
+
+NS_IMPL_ISUPPORTS(nsLayoutHistoryState, nsILayoutHistoryState,
+ nsISupportsWeakReference)
+
+NS_IMETHODIMP
+nsLayoutHistoryState::GetHasStates(bool* aHasStates) {
+ *aHasStates = HasStates();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutHistoryState::GetKeys(nsTArray<nsCString>& aKeys) {
+ if (!HasStates()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendToArray(aKeys, mStates.Keys());
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutHistoryState::GetPresState(const nsACString& aKey, float* aScrollX,
+ float* aScrollY,
+ bool* aAllowScrollOriginDowngrade,
+ float* aRes) {
+ PresState* state = GetState(nsCString(aKey));
+
+ if (!state) {
+ return NS_ERROR_FAILURE;
+ }
+
+ *aScrollX = state->scrollState().x;
+ *aScrollY = state->scrollState().y;
+ *aAllowScrollOriginDowngrade = state->allowScrollOriginDowngrade();
+ *aRes = state->resolution();
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsLayoutHistoryState::AddNewPresState(const nsACString& aKey, float aScrollX,
+ float aScrollY,
+ bool aAllowScrollOriginDowngrade,
+ float aRes) {
+ UniquePtr<PresState> newState = NewPresState();
+ newState->scrollState() = nsPoint(aScrollX, aScrollY);
+ newState->allowScrollOriginDowngrade() = aAllowScrollOriginDowngrade;
+ newState->resolution() = aRes;
+
+ mStates.InsertOrUpdate(nsCString(aKey), std::move(newState));
+
+ return NS_OK;
+}
+
+void nsLayoutHistoryState::AddState(const nsCString& aStateKey,
+ UniquePtr<PresState> aState) {
+ mStates.InsertOrUpdate(aStateKey, std::move(aState));
+}
+
+PresState* nsLayoutHistoryState::GetState(const nsCString& aKey) {
+ auto statePtr = mStates.Lookup(aKey);
+ if (!statePtr) {
+ return nullptr;
+ }
+ PresState* state = statePtr->get();
+
+ if (mScrollPositionOnly) {
+ // Ensure any state that shouldn't be restored is removed
+ state->contentData() = void_t();
+ state->disabledSet() = false;
+ }
+
+ return state;
+}
+
+void nsLayoutHistoryState::RemoveState(const nsCString& aKey) {
+ mStates.Remove(aKey);
+}
+
+bool nsLayoutHistoryState::HasStates() { return mStates.Count() != 0; }
+
+void nsLayoutHistoryState::SetScrollPositionOnly(const bool aFlag) {
+ mScrollPositionOnly = aFlag;
+}
+
+void nsLayoutHistoryState::ResetScrollState() {
+ for (const auto& state : mStates.Values()) {
+ if (state) {
+ state->scrollState() = nsPoint(0, 0);
+ }
+ }
+}
+
+void nsLayoutHistoryState::GetContents(bool* aScrollPositionOnly,
+ nsTArray<nsCString>& aKeys,
+ nsTArray<mozilla::PresState>& aStates) {
+ *aScrollPositionOnly = mScrollPositionOnly;
+ aKeys.SetCapacity(mStates.Count());
+ aStates.SetCapacity(mStates.Count());
+ for (const auto& entry : mStates) {
+ aKeys.AppendElement(entry.GetKey());
+ aStates.AppendElement(*(entry.GetData().get()));
+ }
+}
+
+void nsLayoutHistoryState::Reset() {
+ mScrollPositionOnly = false;
+ mStates.Clear();
+}
+
+namespace mozilla {
+UniquePtr<PresState> NewPresState() {
+ return MakeUnique<PresState>(
+ /* contentData */ mozilla::void_t(),
+ /* scrollState */ nsPoint(0, 0),
+ /* allowScrollOriginDowngrade */ true,
+ /* resolution */ 1.0,
+ /* disabledSet */ false,
+ /* disabled */ false,
+ /* droppedDown */ false);
+}
+} // namespace mozilla
diff --git a/layout/base/nsLayoutUtils.cpp b/layout/base/nsLayoutUtils.cpp
new file mode 100644
index 0000000000..28230421d5
--- /dev/null
+++ b/layout/base/nsLayoutUtils.cpp
@@ -0,0 +1,10003 @@
+/* -*- 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 "nsLayoutUtils.h"
+
+#include <algorithm>
+#include <limits>
+
+#include "ActiveLayerTracker.h"
+#include "DisplayItemClip.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxDrawable.h"
+#include "gfxEnv.h"
+#include "gfxMatrix.h"
+#include "gfxPlatform.h"
+#include "gfxRect.h"
+#include "gfxTypes.h"
+#include "gfxUtils.h"
+#include "ImageContainer.h"
+#include "ImageOps.h"
+#include "ImageRegion.h"
+#include "imgIContainer.h"
+#include "imgIRequest.h"
+#include "LayoutLogging.h"
+#include "MobileViewportManager.h"
+#include "mozilla/AccessibleCaretEventHub.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Baseline.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/dom/AnonymousContent.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/CanvasUtils.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/DOMRect.h"
+#include "mozilla/dom/DOMStringList.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/HTMLCanvasElement.h"
+#include "mozilla/dom/HTMLImageElement.h"
+#include "mozilla/dom/HTMLMediaElementBinding.h"
+#include "mozilla/dom/HTMLVideoElement.h"
+#include "mozilla/dom/InspectorFontFace.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/SVGViewportElement.h"
+#include "mozilla/dom/UIEvent.h"
+#include "mozilla/dom/VideoFrame.h"
+#include "mozilla/dom/VideoFrameBinding.h"
+#include "mozilla/intl/BidiEmbeddingLevel.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EffectSet.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/layers/APZCCallbackHelper.h"
+#include "mozilla/layers/APZPublicUtils.h" // for apz::CalculatePendingDisplayPort
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/PAPZ.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PerfStats.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerMarkers.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/ScrollOrigin.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/ServoStyleSetInlines.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_font.h"
+#include "mozilla/StaticPrefs_general.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_image.h"
+#include "mozilla/StaticPrefs_layers.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/StyleAnimationValue.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/SVGIntegrationUtils.h"
+#include "mozilla/SVGTextFrame.h"
+#include "mozilla/SVGUtils.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ToString.h"
+#include "mozilla/Unused.h"
+#include "mozilla/ViewportFrame.h"
+#include "mozilla/ViewportUtils.h"
+#include "mozilla/WheelHandlingHelper.h" // for WheelHandlingUtils
+#include "nsAnimationManager.h"
+#include "nsAtom.h"
+#include "nsBidiPresUtils.h"
+#include "nsBlockFrame.h"
+#include "nsCanvasFrame.h"
+#include "nsCaret.h"
+#include "nsCharTraits.h"
+#include "nsCOMPtr.h"
+#include "nsComputedDOMStyle.h"
+#include "nsContentUtils.h"
+#include "nsCSSAnonBoxes.h"
+#include "nsCSSColorUtils.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h"
+#include "nsCSSRendering.h"
+#include "nsDisplayList.h"
+#include "nsFieldSetFrame.h"
+#include "nsFlexContainerFrame.h"
+#include "nsFontInflationData.h"
+#include "nsFontMetrics.h"
+#include "nsFrameList.h"
+#include "nsFrameSelection.h"
+#include "nsGenericHTMLElement.h"
+#include "nsGkAtoms.h"
+#include "nsICanvasRenderingContextInternal.h"
+#include "nsIContent.h"
+#include "nsIContentInlines.h"
+#include "nsIDocShell.h"
+#include "nsIDocumentViewer.h"
+#include "nsIFrameInlines.h"
+#include "nsIImageLoadingContent.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIScrollableFrame.h"
+#include "nsIWidget.h"
+#include "nsListControlFrame.h"
+#include "nsPIDOMWindow.h"
+#include "nsPlaceholderFrame.h"
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+#include "nsRefreshDriver.h"
+#include "nsRegion.h"
+#include "nsStyleConsts.h"
+#include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsSubDocumentFrame.h"
+#include "nsTableWrapperFrame.h"
+#include "nsTArray.h"
+#include "nsTextFragment.h"
+#include "nsTextFrame.h"
+#include "nsTHashMap.h"
+#include "nsTransitionManager.h"
+#include "nsView.h"
+#include "nsViewManager.h"
+#include "prenv.h"
+#include "RegionBuilder.h"
+#include "RetainedDisplayListBuilder.h"
+#include "TextDrawTarget.h"
+#include "UnitTransforms.h"
+#include "ViewportFrame.h"
+
+#include "nsXULPopupManager.h"
+
+// Make sure getpid() works.
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h>
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::image;
+using namespace mozilla::layers;
+using namespace mozilla::layout;
+using namespace mozilla::gfx;
+using mozilla::dom::HTMLMediaElement_Binding::HAVE_METADATA;
+using mozilla::dom::HTMLMediaElement_Binding::HAVE_NOTHING;
+
+typedef ScrollableLayerGuid::ViewID ViewID;
+typedef nsStyleTransformMatrix::TransformReferenceBox TransformReferenceBox;
+
+static ViewID sScrollIdCounter = ScrollableLayerGuid::START_SCROLL_ID;
+
+typedef nsTHashMap<nsUint64HashKey, nsIContent*> ContentMap;
+static StaticAutoPtr<ContentMap> sContentMap;
+
+static ContentMap& GetContentMap() {
+ if (!sContentMap) {
+ sContentMap = new ContentMap();
+ }
+ return *sContentMap;
+}
+
+template <typename TestType>
+static bool HasMatchingAnimations(EffectSet& aEffects, TestType&& aTest) {
+ for (KeyframeEffect* effect : aEffects) {
+ if (!effect->GetAnimation() || !effect->GetAnimation()->IsRelevant()) {
+ continue;
+ }
+
+ if (aTest(*effect, aEffects)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template <typename TestType>
+static bool HasMatchingAnimations(const nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet,
+ TestType&& aTest) {
+ MOZ_ASSERT(aFrame);
+
+ if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
+ !aFrame->MayHaveOpacityAnimation()) {
+ return false;
+ }
+
+ if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
+ !aFrame->MayHaveTransformAnimation()) {
+ return false;
+ }
+
+ EffectSet* effectSet = EffectSet::GetForFrame(aFrame, aPropertySet);
+ if (!effectSet) {
+ return false;
+ }
+
+ return HasMatchingAnimations(*effectSet, aTest);
+}
+
+/* static */
+bool nsLayoutUtils::HasAnimationOfPropertySet(
+ const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
+ return HasMatchingAnimations(
+ aFrame, aPropertySet,
+ [&aPropertySet](KeyframeEffect& aEffect, const EffectSet&) {
+ return aEffect.HasAnimationOfPropertySet(aPropertySet);
+ });
+}
+
+/* static */
+bool nsLayoutUtils::HasAnimationOfPropertySet(
+ const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet,
+ EffectSet* aEffectSet) {
+ MOZ_ASSERT(
+ !aEffectSet || EffectSet::GetForFrame(aFrame, aPropertySet) == aEffectSet,
+ "The EffectSet, if supplied, should match what we would otherwise fetch");
+
+ if (!aEffectSet) {
+ return nsLayoutUtils::HasAnimationOfPropertySet(aFrame, aPropertySet);
+ }
+
+ if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::TransformLikeProperties()) &&
+ !aEffectSet->MayHaveTransformAnimation()) {
+ return false;
+ }
+
+ if (aPropertySet.IsSubsetOf(nsCSSPropertyIDSet::OpacityProperties()) &&
+ !aEffectSet->MayHaveOpacityAnimation()) {
+ return false;
+ }
+
+ return HasMatchingAnimations(
+ *aEffectSet,
+ [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
+ return aEffect.HasAnimationOfPropertySet(aPropertySet);
+ });
+}
+
+/* static */
+bool nsLayoutUtils::HasAnimationOfTransformAndMotionPath(
+ const nsIFrame* aFrame) {
+ return nsLayoutUtils::HasAnimationOfPropertySet(
+ aFrame,
+ nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_translate,
+ eCSSProperty_rotate, eCSSProperty_scale,
+ eCSSProperty_offset_path}) ||
+ (!aFrame->StyleDisplay()->mOffsetPath.IsNone() &&
+ nsLayoutUtils::HasAnimationOfPropertySet(
+ aFrame, nsCSSPropertyIDSet::MotionPathProperties()));
+}
+
+/* static */
+bool nsLayoutUtils::HasEffectiveAnimation(
+ const nsIFrame* aFrame, const nsCSSPropertyIDSet& aPropertySet) {
+ return HasMatchingAnimations(
+ aFrame, aPropertySet,
+ [&aPropertySet](KeyframeEffect& aEffect, const EffectSet& aEffectSet) {
+ return aEffect.HasEffectiveAnimationOfPropertySet(aPropertySet,
+ aEffectSet);
+ });
+}
+
+/* static */
+nsCSSPropertyIDSet nsLayoutUtils::GetAnimationPropertiesForCompositor(
+ const nsIFrame* aStyleFrame) {
+ nsCSSPropertyIDSet properties;
+
+ // We fetch the effects for the style frame here since this method is called
+ // by RestyleManager::AddLayerChangesForAnimation which takes care to apply
+ // the relevant hints to the primary frame as needed.
+ EffectSet* effects = EffectSet::GetForStyleFrame(aStyleFrame);
+ if (!effects) {
+ return properties;
+ }
+
+ AnimationPerformanceWarning::Type warning;
+ if (!EffectCompositor::AllowCompositorAnimationsOnFrame(aStyleFrame,
+ warning)) {
+ return properties;
+ }
+
+ for (const KeyframeEffect* effect : *effects) {
+ properties |= effect->GetPropertiesForCompositor(*effects, aStyleFrame);
+ }
+
+ // If properties only have motion-path properties, we have to make sure they
+ // have effects. i.e. offset-path is not none or we have offset-path
+ // animations.
+ if (properties.IsSubsetOf(nsCSSPropertyIDSet::MotionPathProperties()) &&
+ !properties.HasProperty(eCSSProperty_offset_path) &&
+ aStyleFrame->StyleDisplay()->mOffsetPath.IsNone()) {
+ properties.Empty();
+ }
+
+ return properties;
+}
+
+static float GetSuitableScale(float aMaxScale, float aMinScale,
+ nscoord aVisibleDimension,
+ nscoord aDisplayDimension) {
+ float displayVisibleRatio =
+ float(aDisplayDimension) / float(aVisibleDimension);
+ // We want to rasterize based on the largest scale used during the
+ // transform animation, unless that would make us rasterize something
+ // larger than the screen. But we never want to go smaller than the
+ // minimum scale over the animation.
+ if (FuzzyEqualsMultiplicative(displayVisibleRatio, aMaxScale, .01f)) {
+ // Using aMaxScale may make us rasterize something a fraction larger than
+ // the screen. However, if aMaxScale happens to be the final scale of a
+ // transform animation it is better to use aMaxScale so that for the
+ // fraction of a second before we delayerize the composited texture it has
+ // a better chance of being pixel aligned and composited without resampling
+ // (avoiding visually clunky delayerization).
+ return aMaxScale;
+ }
+ return std::max(std::min(aMaxScale, displayVisibleRatio), aMinScale);
+}
+
+// The first value in this pair is the min scale, and the second one is the max
+// scale.
+using MinAndMaxScale = std::pair<MatrixScales, MatrixScales>;
+
+static inline void UpdateMinMaxScale(const nsIFrame* aFrame,
+ const AnimationValue& aValue,
+ MinAndMaxScale& aMinAndMaxScale) {
+ MatrixScales size = aValue.GetScaleValue(aFrame);
+ MatrixScales& minScale = aMinAndMaxScale.first;
+ MatrixScales& maxScale = aMinAndMaxScale.second;
+
+ minScale = Min(minScale, size);
+ maxScale = Max(maxScale, size);
+}
+
+// The final transform matrix is calculated by merging the final results of each
+// transform-like properties, so do the scale factors. In other words, the
+// potential min/max scales could be gotten by multiplying the max/min scales of
+// each properties.
+//
+// For example, there is an animation:
+// from { "transform: scale(1, 1)", "scale: 3, 3" };
+// to { "transform: scale(2, 2)", "scale: 1, 1" };
+//
+// the min scale is (1, 1) * (1, 1) = (1, 1), and
+// The max scale is (2, 2) * (3, 3) = (6, 6).
+// This means we multiply the min/max scale factor of transform property and the
+// min/max scale factor of scale property to get the final max/min scale factor.
+static Array<MinAndMaxScale, 2> GetMinAndMaxScaleForAnimationProperty(
+ const nsIFrame* aFrame,
+ const nsTArray<RefPtr<dom::Animation>>& aAnimations) {
+ // We use a fixed array to store the min/max scales for each property.
+ // The first element in the array is for eCSSProperty_transform, and the
+ // second one is for eCSSProperty_scale.
+ const MinAndMaxScale defaultValue =
+ std::make_pair(MatrixScales(std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max()),
+ MatrixScales(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min()));
+ Array<MinAndMaxScale, 2> minAndMaxScales(defaultValue, defaultValue);
+
+ for (dom::Animation* anim : aAnimations) {
+ // This method is only expected to be passed animations that are running on
+ // the compositor and we only pass playing animations to the compositor,
+ // which are, by definition, "relevant" animations (animations that are
+ // not yet finished or which are filling forwards).
+ MOZ_ASSERT(anim->IsRelevant());
+
+ const dom::KeyframeEffect* effect =
+ anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
+ MOZ_ASSERT(effect, "A playing animation should have a keyframe effect");
+ for (const AnimationProperty& prop : effect->Properties()) {
+ if (prop.mProperty.mID != eCSSProperty_transform &&
+ prop.mProperty.mID != eCSSProperty_scale) {
+ continue;
+ }
+
+ // 0: eCSSProperty_transform.
+ // 1: eCSSProperty_scale.
+ MinAndMaxScale& scales =
+ minAndMaxScales[prop.mProperty.mID == eCSSProperty_transform ? 0 : 1];
+
+ // We need to factor in the scale of the base style if the base style
+ // will be used on the compositor.
+ const AnimationValue& baseStyle = effect->BaseStyle(prop.mProperty);
+ if (!baseStyle.IsNull()) {
+ UpdateMinMaxScale(aFrame, baseStyle, scales);
+ }
+
+ for (const AnimationPropertySegment& segment : prop.mSegments) {
+ // In case of add or accumulate composite, StyleAnimationValue does
+ // not have a valid value.
+ if (segment.HasReplaceableFromValue()) {
+ UpdateMinMaxScale(aFrame, segment.mFromValue, scales);
+ }
+
+ if (segment.HasReplaceableToValue()) {
+ UpdateMinMaxScale(aFrame, segment.mToValue, scales);
+ }
+ }
+ }
+ }
+
+ return minAndMaxScales;
+}
+
+MatrixScales nsLayoutUtils::ComputeSuitableScaleForAnimation(
+ const nsIFrame* aFrame, const nsSize& aVisibleSize,
+ const nsSize& aDisplaySize) {
+ const nsTArray<RefPtr<dom::Animation>> compositorAnimations =
+ EffectCompositor::GetAnimationsForCompositor(
+ aFrame,
+ nsCSSPropertyIDSet{eCSSProperty_transform, eCSSProperty_scale});
+
+ if (compositorAnimations.IsEmpty()) {
+ return MatrixScales();
+ }
+
+ const Array<MinAndMaxScale, 2> minAndMaxScales =
+ GetMinAndMaxScaleForAnimationProperty(aFrame, compositorAnimations);
+
+ // This might cause an issue if users use std::numeric_limits<float>::min()
+ // (or max()) as the scale value. However, in this case, we may render an
+ // extreme small (or large) element, so this may not be a problem. If so,
+ // please fix this.
+ MatrixScales maxScale(std::numeric_limits<float>::min(),
+ std::numeric_limits<float>::min());
+ MatrixScales minScale(std::numeric_limits<float>::max(),
+ std::numeric_limits<float>::max());
+
+ auto isUnset = [](const MatrixScales& aMax, const MatrixScales& aMin) {
+ return aMax.xScale == std::numeric_limits<float>::min() &&
+ aMax.yScale == std::numeric_limits<float>::min() &&
+ aMin.xScale == std::numeric_limits<float>::max() &&
+ aMin.yScale == std::numeric_limits<float>::max();
+ };
+
+ // Iterate the slots to get the final scale value.
+ for (const auto& pair : minAndMaxScales) {
+ const MatrixScales& currMinScale = pair.first;
+ const MatrixScales& currMaxScale = pair.second;
+
+ if (isUnset(currMaxScale, currMinScale)) {
+ // We don't have this animation property, so skip.
+ continue;
+ }
+
+ if (isUnset(maxScale, minScale)) {
+ // Initialize maxScale and minScale.
+ maxScale = currMaxScale;
+ minScale = currMinScale;
+ } else {
+ // The scale factors of each transform-like property should be multiplied
+ // by others because we merge their sampled values as a final matrix by
+ // matrix multiplication, so here we multiply the scale factors by the
+ // previous one to get the possible max and min scale factors.
+ maxScale = maxScale * currMaxScale;
+ minScale = minScale * currMinScale;
+ }
+ }
+
+ if (isUnset(maxScale, minScale)) {
+ // We didn't encounter any transform-like property.
+ return MatrixScales();
+ }
+
+ return MatrixScales(
+ GetSuitableScale(maxScale.xScale, minScale.xScale, aVisibleSize.width,
+ aDisplaySize.width),
+ GetSuitableScale(maxScale.yScale, minScale.yScale, aVisibleSize.height,
+ aDisplaySize.height));
+}
+
+bool nsLayoutUtils::AreAsyncAnimationsEnabled() {
+ return StaticPrefs::layers_offmainthreadcomposition_async_animations() &&
+ gfxPlatform::OffMainThreadCompositingEnabled();
+}
+
+bool nsLayoutUtils::AreRetainedDisplayListsEnabled() {
+#ifdef MOZ_WIDGET_ANDROID
+ return StaticPrefs::layout_display_list_retain();
+#else
+ if (XRE_IsContentProcess()) {
+ return StaticPrefs::layout_display_list_retain();
+ }
+
+ if (XRE_IsE10sParentProcess()) {
+ return StaticPrefs::layout_display_list_retain_chrome();
+ }
+
+ // Retained display lists require e10s.
+ return false;
+#endif
+}
+
+bool nsLayoutUtils::DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame) {
+ return GetRetainedDisplayListBuilder(aFrame) != nullptr;
+}
+
+RetainedDisplayListBuilder* nsLayoutUtils::GetRetainedDisplayListBuilder(
+ nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->PresShell());
+
+ // Use the pres shell root frame to get the display root frame. This skips
+ // the early exit in |nsLayoutUtils::GetDisplayRootFrame()| for popup frames.
+ const nsIFrame* rootFrame = aFrame->PresShell()->GetRootFrame();
+ if (!rootFrame) {
+ return nullptr;
+ }
+
+ const nsIFrame* displayRootFrame = GetDisplayRootFrame(rootFrame);
+ MOZ_ASSERT(displayRootFrame);
+
+ return displayRootFrame->GetProperty(RetainedDisplayListBuilder::Cached());
+}
+
+bool nsLayoutUtils::GPUImageScalingEnabled() {
+ static bool sGPUImageScalingEnabled;
+ static bool sGPUImageScalingPrefInitialised = false;
+
+ if (!sGPUImageScalingPrefInitialised) {
+ sGPUImageScalingPrefInitialised = true;
+ sGPUImageScalingEnabled =
+ Preferences::GetBool("layout.gpu-image-scaling.enabled", false);
+ }
+
+ return sGPUImageScalingEnabled;
+}
+
+void nsLayoutUtils::UnionChildOverflow(nsIFrame* aFrame,
+ OverflowAreas& aOverflowAreas,
+ FrameChildListIDs aSkipChildLists) {
+ // Iterate over all children except pop-ups.
+ FrameChildListIDs skip(aSkipChildLists);
+ skip += FrameChildListID::Popup;
+
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (skip.contains(listID)) {
+ continue;
+ }
+ for (nsIFrame* child : list) {
+ aOverflowAreas.UnionWith(
+ child->GetActualAndNormalOverflowAreasRelativeToParent());
+ }
+ }
+}
+
+static void DestroyViewID(void* aObject, nsAtom* aPropertyName,
+ void* aPropertyValue, void* aData) {
+ ViewID* id = static_cast<ViewID*>(aPropertyValue);
+ GetContentMap().Remove(*id);
+ delete id;
+}
+
+/**
+ * A namespace class for static layout utilities.
+ */
+
+bool nsLayoutUtils::FindIDFor(const nsIContent* aContent, ViewID* aOutViewId) {
+ void* scrollIdProperty = aContent->GetProperty(nsGkAtoms::RemoteId);
+ if (scrollIdProperty) {
+ *aOutViewId = *static_cast<ViewID*>(scrollIdProperty);
+ return true;
+ }
+ return false;
+}
+
+ViewID nsLayoutUtils::FindOrCreateIDFor(nsIContent* aContent) {
+ ViewID scrollId;
+
+ if (!FindIDFor(aContent, &scrollId)) {
+ scrollId = sScrollIdCounter++;
+ aContent->SetProperty(nsGkAtoms::RemoteId, new ViewID(scrollId),
+ DestroyViewID);
+ GetContentMap().InsertOrUpdate(scrollId, aContent);
+ }
+
+ return scrollId;
+}
+
+nsIContent* nsLayoutUtils::FindContentFor(ViewID aId) {
+ MOZ_ASSERT(aId != ScrollableLayerGuid::NULL_SCROLL_ID,
+ "Cannot find a content element in map for null IDs.");
+ nsIContent* content;
+ bool exists = GetContentMap().Get(aId, &content);
+
+ if (exists) {
+ return content;
+ } else {
+ return nullptr;
+ }
+}
+
+nsIFrame* nsLayoutUtils::GetScrollFrameFromContent(nsIContent* aContent) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (aContent->OwnerDoc()->GetRootElement() == aContent) {
+ PresShell* presShell = frame ? frame->PresShell() : nullptr;
+ if (!presShell) {
+ presShell = aContent->OwnerDoc()->GetPresShell();
+ }
+ // We want the scroll frame, the root scroll frame differs from all
+ // others in that the primary frame is not the scroll frame.
+ nsIFrame* rootScrollFrame =
+ presShell ? presShell->GetRootScrollFrame() : nullptr;
+ if (rootScrollFrame) {
+ frame = rootScrollFrame;
+ }
+ }
+ return frame;
+}
+
+nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(
+ nsIContent* aContent) {
+ nsIFrame* scrollFrame = GetScrollFrameFromContent(aContent);
+ return scrollFrame ? scrollFrame->GetScrollTargetFrame() : nullptr;
+}
+
+nsIScrollableFrame* nsLayoutUtils::FindScrollableFrameFor(ViewID aId) {
+ nsIContent* content = FindContentFor(aId);
+ if (!content) {
+ return nullptr;
+ }
+
+ return FindScrollableFrameFor(content);
+}
+
+ViewID nsLayoutUtils::FindIDForScrollableFrame(
+ nsIScrollableFrame* aScrollable) {
+ if (!aScrollable) {
+ return ScrollableLayerGuid::NULL_SCROLL_ID;
+ }
+
+ nsIFrame* scrollFrame = do_QueryFrame(aScrollable);
+ nsIContent* scrollContent = scrollFrame->GetContent();
+
+ ScrollableLayerGuid::ViewID scrollId;
+ if (scrollContent && nsLayoutUtils::FindIDFor(scrollContent, &scrollId)) {
+ return scrollId;
+ }
+
+ return ScrollableLayerGuid::NULL_SCROLL_ID;
+}
+
+bool nsLayoutUtils::UsesAsyncScrolling(nsIFrame* aFrame) {
+#ifdef MOZ_WIDGET_ANDROID
+ // We always have async scrolling for android
+ return true;
+#endif
+
+ return AsyncPanZoomEnabled(aFrame);
+}
+
+bool nsLayoutUtils::AsyncPanZoomEnabled(const nsIFrame* aFrame) {
+ // We use this as a shortcut, since if the compositor will never use APZ,
+ // no widget will either.
+ if (!gfxPlatform::AsyncPanZoomEnabled()) {
+ return false;
+ }
+
+ const nsIFrame* frame = nsLayoutUtils::GetDisplayRootFrame(aFrame);
+ nsIWidget* widget = frame->GetNearestWidget();
+ if (!widget) {
+ return false;
+ }
+ return widget->AsyncPanZoomEnabled();
+}
+
+bool nsLayoutUtils::AllowZoomingForDocument(
+ const mozilla::dom::Document* aDocument) {
+ if (aDocument->GetPresShell() &&
+ !aDocument->GetPresShell()->AsyncPanZoomEnabled()) {
+ return false;
+ }
+ // True if we allow zooming for all documents on this platform, or if we are
+ // in RDM and handling meta viewports, which force zoom under some
+ // circumstances.
+ BrowsingContext* bc = aDocument ? aDocument->GetBrowsingContext() : nullptr;
+ return StaticPrefs::apz_allow_zooming() ||
+ (bc && bc->InRDMPane() &&
+ nsLayoutUtils::ShouldHandleMetaViewport(aDocument));
+}
+
+static bool HasVisibleAnonymousContents(Document* aDoc) {
+ for (RefPtr<AnonymousContent>& ac : aDoc->GetAnonymousContents()) {
+ // We check to see if the anonymous content node has a frame. If it doesn't,
+ // that means that's not visible to the user because e.g. it's display:none.
+ // For now we assume that if it has a frame, it is visible. We might be able
+ // to refine this further by adding complexity if it turns out this
+ // condition results in a lot of false positives.
+ if (ac->Host()->GetPrimaryFrame()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsLayoutUtils::ShouldDisableApzForElement(nsIContent* aContent) {
+ if (!aContent) {
+ return false;
+ }
+
+ if (aContent->GetProperty(nsGkAtoms::apzDisabled)) {
+ return true;
+ }
+
+ Document* doc = aContent->GetComposedDoc();
+ if (PresShell* rootPresShell =
+ APZCCallbackHelper::GetRootContentDocumentPresShellForContent(
+ aContent)) {
+ if (Document* rootDoc = rootPresShell->GetDocument()) {
+ nsIContent* rootContent =
+ rootPresShell->GetRootScrollFrame()
+ ? rootPresShell->GetRootScrollFrame()->GetContent()
+ : rootDoc->GetDocumentElement();
+ // For the AccessibleCaret and other anonymous contents: disable APZ on
+ // any scrollable subframes that are not the root scrollframe of a
+ // document, if the document has any visible anonymous contents.
+ //
+ // If we find this is triggering in too many scenarios then we might
+ // want to tighten this check further. The main use cases for which we
+ // want to disable APZ as of this writing are listed in bug 1316318.
+ if (aContent != rootContent && HasVisibleAnonymousContents(rootDoc)) {
+ return true;
+ }
+ }
+ }
+
+ if (!doc) {
+ return false;
+ }
+
+ if (PresShell* presShell = doc->GetPresShell()) {
+ if (RefPtr<AccessibleCaretEventHub> eventHub =
+ presShell->GetAccessibleCaretEventHub()) {
+ // Disable APZ for all elements if AccessibleCaret tells us to do so.
+ if (eventHub->ShouldDisableApz()) {
+ return true;
+ }
+ }
+ }
+
+ return StaticPrefs::apz_disable_for_scroll_linked_effects() &&
+ doc->HasScrollLinkedEffect();
+}
+
+void nsLayoutUtils::NotifyPaintSkipTransaction(ViewID aScrollId) {
+ if (nsIScrollableFrame* scrollFrame =
+ nsLayoutUtils::FindScrollableFrameFor(aScrollId)) {
+#ifdef DEBUG
+ nsIFrame* f = do_QueryFrame(scrollFrame);
+ MOZ_ASSERT(f && f->PresShell() && !f->PresShell()->IsResolutionUpdated());
+#endif
+ scrollFrame->NotifyApzTransaction();
+ }
+}
+
+nsContainerFrame* nsLayoutUtils::LastContinuationWithChild(
+ nsContainerFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "NULL frame pointer");
+ for (auto f = aFrame->LastContinuation(); f; f = f->GetPrevContinuation()) {
+ for (const auto& childList : f->ChildLists()) {
+ if (MOZ_LIKELY(!childList.mList.IsEmpty())) {
+ return static_cast<nsContainerFrame*>(f);
+ }
+ }
+ }
+ return aFrame;
+}
+
+// static
+FrameChildListID nsLayoutUtils::GetChildListNameFor(nsIFrame* aChildFrame) {
+ FrameChildListID id = FrameChildListID::Principal;
+
+ MOZ_DIAGNOSTIC_ASSERT(!aChildFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW));
+
+ if (aChildFrame->HasAnyStateBits(NS_FRAME_IS_OVERFLOW_CONTAINER)) {
+ nsIFrame* pif = aChildFrame->GetPrevInFlow();
+ if (pif->GetParent() == aChildFrame->GetParent()) {
+ id = FrameChildListID::ExcessOverflowContainers;
+ } else {
+ id = FrameChildListID::OverflowContainers;
+ }
+ } else {
+ LayoutFrameType childType = aChildFrame->Type();
+ if (LayoutFrameType::TableColGroup == childType) {
+ id = FrameChildListID::ColGroup;
+ } else if (aChildFrame->IsTableCaption()) {
+ id = FrameChildListID::Caption;
+ } else {
+ id = FrameChildListID::Principal;
+ }
+ }
+
+#ifdef DEBUG
+ // Verify that the frame is actually in that child list or in the
+ // corresponding overflow list.
+ nsContainerFrame* parent = aChildFrame->GetParent();
+ bool found = parent->GetChildList(id).ContainsFrame(aChildFrame);
+ if (!found) {
+ found = parent->GetChildList(FrameChildListID::Overflow)
+ .ContainsFrame(aChildFrame);
+ MOZ_ASSERT(found, "not in child list");
+ }
+#endif
+
+ return id;
+}
+
+static Element* GetPseudo(const nsIContent* aContent, nsAtom* aPseudoProperty) {
+ MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
+ aPseudoProperty == nsGkAtoms::afterPseudoProperty ||
+ aPseudoProperty == nsGkAtoms::markerPseudoProperty);
+ if (!aContent->MayHaveAnonymousChildren()) {
+ return nullptr;
+ }
+ return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
+}
+
+/*static*/
+Element* nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent) {
+ return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
+}
+
+/*static*/
+nsIFrame* nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent) {
+ Element* pseudo = GetBeforePseudo(aContent);
+ return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+}
+
+/*static*/
+Element* nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent) {
+ return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
+}
+
+/*static*/
+nsIFrame* nsLayoutUtils::GetAfterFrame(const nsIContent* aContent) {
+ Element* pseudo = GetAfterPseudo(aContent);
+ return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+}
+
+/*static*/
+Element* nsLayoutUtils::GetMarkerPseudo(const nsIContent* aContent) {
+ return GetPseudo(aContent, nsGkAtoms::markerPseudoProperty);
+}
+
+/*static*/
+nsIFrame* nsLayoutUtils::GetMarkerFrame(const nsIContent* aContent) {
+ Element* pseudo = GetMarkerPseudo(aContent);
+ return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+}
+
+#ifdef ACCESSIBILITY
+void nsLayoutUtils::GetMarkerSpokenText(const nsIContent* aContent,
+ nsAString& aText) {
+ MOZ_ASSERT(aContent && aContent->IsGeneratedContentContainerForMarker());
+
+ aText.Truncate();
+
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return;
+ }
+
+ if (frame->StyleContent()->ContentCount() > 0) {
+ for (nsIFrame* child : frame->PrincipalChildList()) {
+ nsIFrame::RenderedText text = child->GetRenderedText();
+ aText += text.mString;
+ }
+ return;
+ }
+
+ if (!frame->StyleList()->mListStyleImage.IsNone()) {
+ // ::marker is an image, so use default bullet character.
+ static const char16_t kDiscMarkerString[] = {0x2022, ' ', 0};
+ aText.AssignLiteral(kDiscMarkerString);
+ return;
+ }
+
+ frame->PresContext()
+ ->FrameConstructor()
+ ->GetContainStyleScopeManager()
+ .GetSpokenCounterText(frame, aText);
+}
+#endif
+
+// static
+nsIFrame* nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
+ LayoutFrameType aFrameType,
+ nsIFrame* aStopAt) {
+ for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
+ if (frame->Type() == aFrameType) {
+ return frame;
+ }
+ if (frame == aStopAt) {
+ break;
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetPageFrame(nsIFrame* aFrame) {
+ return GetClosestFrameOfType(aFrame, LayoutFrameType::Page);
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetStyleFrame(nsIFrame* aPrimaryFrame) {
+ MOZ_ASSERT(aPrimaryFrame);
+ if (aPrimaryFrame->IsTableWrapperFrame()) {
+ nsIFrame* inner = aPrimaryFrame->PrincipalChildList().FirstChild();
+ // inner may be null, if aPrimaryFrame is mid-destruction
+ return inner;
+ }
+
+ return aPrimaryFrame;
+}
+
+const nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIFrame* aPrimaryFrame) {
+ return nsLayoutUtils::GetStyleFrame(const_cast<nsIFrame*>(aPrimaryFrame));
+}
+
+nsIFrame* nsLayoutUtils::GetStyleFrame(const nsIContent* aContent) {
+ nsIFrame* frame = aContent->GetPrimaryFrame();
+ if (!frame) {
+ return nullptr;
+ }
+
+ return nsLayoutUtils::GetStyleFrame(frame);
+}
+
+CSSIntCoord nsLayoutUtils::UnthemedScrollbarSize(StyleScrollbarWidth aWidth) {
+ switch (aWidth) {
+ case StyleScrollbarWidth::Auto:
+ return 12;
+ case StyleScrollbarWidth::Thin:
+ return 6;
+ case StyleScrollbarWidth::None:
+ return 0;
+ }
+ return 0;
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(nsIFrame* aStyleFrame) {
+ nsIFrame* parent = aStyleFrame->GetParent();
+ return parent && parent->IsTableWrapperFrame() ? parent : aStyleFrame;
+}
+
+/* static */
+const nsIFrame* nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
+ const nsIFrame* aStyleFrame) {
+ return nsLayoutUtils::GetPrimaryFrameFromStyleFrame(
+ const_cast<nsIFrame*>(aStyleFrame));
+}
+
+/*static*/
+bool nsLayoutUtils::IsPrimaryStyleFrame(const nsIFrame* aFrame) {
+ if (aFrame->IsTableWrapperFrame()) {
+ return false;
+ }
+
+ const nsIFrame* parent = aFrame->GetParent();
+ if (parent && parent->IsTableWrapperFrame()) {
+ return parent->PrincipalChildList().FirstChild() == aFrame;
+ }
+
+ return aFrame->IsPrimaryFrame();
+}
+
+nsIFrame* nsLayoutUtils::GetFloatFromPlaceholder(nsIFrame* aFrame) {
+ NS_ASSERTION(aFrame->IsPlaceholderFrame(), "Must have a placeholder here");
+ if (aFrame->HasAnyStateBits(PLACEHOLDER_FOR_FLOAT)) {
+ nsIFrame* outOfFlowFrame =
+ nsPlaceholderFrame::GetRealFrameForPlaceholder(aFrame);
+ NS_ASSERTION(outOfFlowFrame && outOfFlowFrame->IsFloating(),
+ "How did that happen?");
+ return outOfFlowFrame;
+ }
+
+ return nullptr;
+}
+
+// static
+nsIFrame* nsLayoutUtils::GetCrossDocParentFrameInProcess(
+ const nsIFrame* aFrame, nsPoint* aCrossDocOffset) {
+ nsIFrame* p = aFrame->GetParent();
+ if (p) {
+ return p;
+ }
+
+ nsView* v = aFrame->GetView();
+ if (!v) {
+ return nullptr;
+ }
+ v = v->GetParent(); // anonymous inner view
+ if (!v) {
+ return nullptr;
+ }
+ v = v->GetParent(); // subdocumentframe's view
+ if (!v) {
+ return nullptr;
+ }
+
+ p = v->GetFrame();
+ if (p && aCrossDocOffset) {
+ nsSubDocumentFrame* subdocumentFrame = do_QueryFrame(p);
+ MOZ_ASSERT(subdocumentFrame);
+ *aCrossDocOffset += subdocumentFrame->GetExtraOffset();
+ }
+
+ return p;
+}
+
+// static
+nsIFrame* nsLayoutUtils::GetCrossDocParentFrame(const nsIFrame* aFrame,
+ nsPoint* aCrossDocOffset) {
+ return GetCrossDocParentFrameInProcess(aFrame, aCrossDocOffset);
+}
+
+// static
+bool nsLayoutUtils::IsProperAncestorFrameCrossDoc(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor) {
+ if (aFrame == aAncestorFrame) return false;
+ return IsAncestorFrameCrossDoc(aAncestorFrame, aFrame, aCommonAncestor);
+}
+
+// static
+bool nsLayoutUtils::IsProperAncestorFrameCrossDocInProcess(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor) {
+ if (aFrame == aAncestorFrame) return false;
+ return IsAncestorFrameCrossDocInProcess(aAncestorFrame, aFrame,
+ aCommonAncestor);
+}
+
+// static
+bool nsLayoutUtils::IsAncestorFrameCrossDoc(const nsIFrame* aAncestorFrame,
+ const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor) {
+ for (const nsIFrame* f = aFrame; f != aCommonAncestor;
+ f = GetCrossDocParentFrameInProcess(f)) {
+ if (f == aAncestorFrame) return true;
+ }
+ return aCommonAncestor == aAncestorFrame;
+}
+
+// static
+bool nsLayoutUtils::IsAncestorFrameCrossDocInProcess(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor) {
+ for (const nsIFrame* f = aFrame; f != aCommonAncestor;
+ f = GetCrossDocParentFrameInProcess(f)) {
+ if (f == aAncestorFrame) return true;
+ }
+ return aCommonAncestor == aAncestorFrame;
+}
+
+// static
+bool nsLayoutUtils::IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
+ const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor) {
+ if (aFrame == aAncestorFrame) return false;
+ for (const nsIFrame* f = aFrame; f != aCommonAncestor; f = f->GetParent()) {
+ if (f == aAncestorFrame) return true;
+ }
+ return aCommonAncestor == aAncestorFrame;
+}
+
+// static
+nsIFrame* nsLayoutUtils::FillAncestors(nsIFrame* aFrame,
+ nsIFrame* aStopAtAncestor,
+ nsTArray<nsIFrame*>* aAncestors) {
+ while (aFrame && aFrame != aStopAtAncestor) {
+ aAncestors->AppendElement(aFrame);
+ aFrame = nsLayoutUtils::GetParentOrPlaceholderFor(aFrame);
+ }
+ return aFrame;
+}
+
+// Return true if aFrame1 is after aFrame2
+static bool IsFrameAfter(nsIFrame* aFrame1, nsIFrame* aFrame2) {
+ nsIFrame* f = aFrame2;
+ do {
+ f = f->GetNextSibling();
+ if (f == aFrame1) return true;
+ } while (f);
+ return false;
+}
+
+// static
+int32_t nsLayoutUtils::DoCompareTreePosition(nsIFrame* aFrame1,
+ nsIFrame* aFrame2,
+ nsIFrame* aCommonAncestor) {
+ MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
+ MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
+
+ AutoTArray<nsIFrame*, 20> frame2Ancestors;
+ nsIFrame* nonCommonAncestor =
+ FillAncestors(aFrame2, aCommonAncestor, &frame2Ancestors);
+ return DoCompareTreePosition(aFrame1, aFrame2, frame2Ancestors,
+ nonCommonAncestor ? aCommonAncestor : nullptr);
+}
+
+// static
+int32_t nsLayoutUtils::DoCompareTreePosition(
+ nsIFrame* aFrame1, nsIFrame* aFrame2, nsTArray<nsIFrame*>& aFrame2Ancestors,
+ nsIFrame* aCommonAncestor) {
+ MOZ_ASSERT(aFrame1, "aFrame1 must not be null");
+ MOZ_ASSERT(aFrame2, "aFrame2 must not be null");
+
+ nsPresContext* presContext = aFrame1->PresContext();
+ if (presContext != aFrame2->PresContext()) {
+ NS_ERROR("no common ancestor at all, different documents");
+ return 0;
+ }
+
+ AutoTArray<nsIFrame*, 20> frame1Ancestors;
+ if (aCommonAncestor &&
+ !FillAncestors(aFrame1, aCommonAncestor, &frame1Ancestors)) {
+ // We reached the root of the frame tree ... if aCommonAncestor was set,
+ // it is wrong
+ return DoCompareTreePosition(aFrame1, aFrame2, nullptr);
+ }
+
+ int32_t last1 = int32_t(frame1Ancestors.Length()) - 1;
+ int32_t last2 = int32_t(aFrame2Ancestors.Length()) - 1;
+ while (last1 >= 0 && last2 >= 0 &&
+ frame1Ancestors[last1] == aFrame2Ancestors[last2]) {
+ last1--;
+ last2--;
+ }
+
+ if (last1 < 0) {
+ if (last2 < 0) {
+ NS_ASSERTION(aFrame1 == aFrame2, "internal error?");
+ return 0;
+ }
+ // aFrame1 is an ancestor of aFrame2
+ return -1;
+ }
+
+ if (last2 < 0) {
+ // aFrame2 is an ancestor of aFrame1
+ return 1;
+ }
+
+ nsIFrame* ancestor1 = frame1Ancestors[last1];
+ nsIFrame* ancestor2 = aFrame2Ancestors[last2];
+ // Now we should be able to walk sibling chains to find which one is first
+ if (IsFrameAfter(ancestor2, ancestor1)) {
+ return -1;
+ }
+ if (IsFrameAfter(ancestor1, ancestor2)) {
+ return 1;
+ }
+ NS_WARNING("Frames were in different child lists???");
+ return 0;
+}
+
+// static
+nsIFrame* nsLayoutUtils::GetLastSibling(nsIFrame* aFrame) {
+ if (!aFrame) {
+ return nullptr;
+ }
+
+ nsIFrame* next;
+ while ((next = aFrame->GetNextSibling()) != nullptr) {
+ aFrame = next;
+ }
+ return aFrame;
+}
+
+// static
+nsView* nsLayoutUtils::FindSiblingViewFor(nsView* aParentView,
+ nsIFrame* aFrame) {
+ nsIFrame* parentViewFrame = aParentView->GetFrame();
+ nsIContent* parentViewContent =
+ parentViewFrame ? parentViewFrame->GetContent() : nullptr;
+ for (nsView* insertBefore = aParentView->GetFirstChild(); insertBefore;
+ insertBefore = insertBefore->GetNextSibling()) {
+ nsIFrame* f = insertBefore->GetFrame();
+ if (!f) {
+ // this view could be some anonymous view attached to a meaningful parent
+ for (nsView* searchView = insertBefore->GetParent(); searchView;
+ searchView = searchView->GetParent()) {
+ f = searchView->GetFrame();
+ if (f) {
+ break;
+ }
+ }
+ NS_ASSERTION(f, "Can't find a frame anywhere!");
+ }
+ if (!f || !aFrame->GetContent() || !f->GetContent() ||
+ nsContentUtils::CompareTreePosition<TreeKind::Flat>(
+ aFrame->GetContent(), f->GetContent(), parentViewContent) > 0) {
+ // aFrame's content is after f's content (or we just don't know),
+ // so put our view before f's view
+ return insertBefore;
+ }
+ }
+ return nullptr;
+}
+
+// static
+nsIScrollableFrame* nsLayoutUtils::GetScrollableFrameFor(
+ const nsIFrame* aScrolledFrame) {
+ nsIFrame* frame = aScrolledFrame->GetParent();
+ nsIScrollableFrame* sf = do_QueryFrame(frame);
+ return (sf && sf->GetScrolledFrame() == aScrolledFrame) ? sf : nullptr;
+}
+
+/* static */
+SideBits nsLayoutUtils::GetSideBitsForFixedPositionContent(
+ const nsIFrame* aFixedPosFrame) {
+ SideBits sides = SideBits::eNone;
+ if (aFixedPosFrame) {
+ const nsStylePosition* position = aFixedPosFrame->StylePosition();
+ if (!position->mOffset.Get(eSideRight).IsAuto()) {
+ sides |= SideBits::eRight;
+ }
+ if (!position->mOffset.Get(eSideLeft).IsAuto()) {
+ sides |= SideBits::eLeft;
+ }
+ if (!position->mOffset.Get(eSideBottom).IsAuto()) {
+ sides |= SideBits::eBottom;
+ }
+ if (!position->mOffset.Get(eSideTop).IsAuto()) {
+ sides |= SideBits::eTop;
+ }
+ }
+ return sides;
+}
+
+ScrollableLayerGuid::ViewID nsLayoutUtils::ScrollIdForRootScrollFrame(
+ nsPresContext* aPresContext) {
+ ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (nsIFrame* rootScrollFrame =
+ aPresContext->PresShell()->GetRootScrollFrame()) {
+ if (nsIContent* content = rootScrollFrame->GetContent()) {
+ id = FindOrCreateIDFor(content);
+ }
+ }
+ return id;
+}
+
+// static
+nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrameForDirection(
+ nsIFrame* aFrame, ScrollDirections aDirections) {
+ NS_ASSERTION(
+ aFrame, "GetNearestScrollableFrameForDirection expects a non-null frame");
+ // FIXME Bug 1714720 : This nearest scroll target is not going to work over
+ // process boundaries, in such cases we need to hand over in APZ side.
+ for (nsIFrame* f = aFrame; f;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ nsIScrollableFrame* scrollableFrame = do_QueryFrame(f);
+ if (scrollableFrame) {
+ ScrollDirections directions =
+ scrollableFrame->GetAvailableScrollingDirectionsForUserInputEvents();
+ if (aDirections.contains(ScrollDirection::eVertical)) {
+ if (directions.contains(ScrollDirection::eVertical)) {
+ return scrollableFrame;
+ }
+ }
+ if (aDirections.contains(ScrollDirection::eHorizontal)) {
+ if (directions.contains(ScrollDirection::eHorizontal)) {
+ return scrollableFrame;
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+static nsIFrame* GetNearestScrollableOrOverflowClipFrame(
+ nsIFrame* aFrame, uint32_t aFlags,
+ const std::function<bool(const nsIFrame* aCurrentFrame)>& aClipFrameCheck =
+ nullptr) {
+ MOZ_ASSERT(
+ aFrame,
+ "GetNearestScrollableOrOverflowClipFrame expects a non-null frame");
+
+ auto GetNextFrame = [aFlags](const nsIFrame* aFrame) -> nsIFrame* {
+ return (aFlags & nsLayoutUtils::SCROLLABLE_SAME_DOC)
+ ? aFrame->GetParent()
+ : nsLayoutUtils::GetCrossDocParentFrameInProcess(aFrame);
+ };
+
+ for (nsIFrame* f = aFrame; f; f = GetNextFrame(f)) {
+ if (aClipFrameCheck && aClipFrameCheck(f)) {
+ return f;
+ }
+
+ if ((aFlags & nsLayoutUtils::SCROLLABLE_STOP_AT_PAGE) && f->IsPageFrame()) {
+ break;
+ }
+ if (nsIScrollableFrame* scrollableFrame = do_QueryFrame(f)) {
+ if (aFlags & nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE) {
+ if (scrollableFrame->WantAsyncScroll()) {
+ return f;
+ }
+ } else {
+ ScrollStyles ss = scrollableFrame->GetScrollStyles();
+ if ((aFlags & nsLayoutUtils::SCROLLABLE_INCLUDE_HIDDEN) ||
+ ss.mVertical != StyleOverflow::Hidden ||
+ ss.mHorizontal != StyleOverflow::Hidden) {
+ return f;
+ }
+ }
+ if (aFlags & nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT) {
+ PresShell* presShell = f->PresShell();
+ if (presShell->GetRootScrollFrame() == f && presShell->GetDocument() &&
+ presShell->GetDocument()->IsRootDisplayDocument()) {
+ return f;
+ }
+ }
+ }
+ if ((aFlags & nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT) &&
+ f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
+ nsLayoutUtils::IsReallyFixedPos(f)) {
+ return f->PresShell()->GetRootScrollFrame();
+ }
+ }
+ return nullptr;
+}
+
+// static
+nsIScrollableFrame* nsLayoutUtils::GetNearestScrollableFrame(nsIFrame* aFrame,
+ uint32_t aFlags) {
+ nsIFrame* found = GetNearestScrollableOrOverflowClipFrame(aFrame, aFlags);
+ if (!found) {
+ return nullptr;
+ }
+
+ return do_QueryFrame(found);
+}
+
+// static
+nsIFrame* nsLayoutUtils::GetNearestOverflowClipFrame(nsIFrame* aFrame) {
+ return GetNearestScrollableOrOverflowClipFrame(
+ aFrame, SCROLLABLE_SAME_DOC | SCROLLABLE_INCLUDE_HIDDEN,
+ [](const nsIFrame* currentFrame) -> bool {
+ // In cases of SVG Inner/Outer frames it basically clips descendants
+ // unless overflow: visible is explicitly specified.
+ LayoutFrameType type = currentFrame->Type();
+ return ((type == LayoutFrameType::SVGOuterSVG ||
+ type == LayoutFrameType::SVGInnerSVG) &&
+ (currentFrame->StyleDisplay()->mOverflowX !=
+ StyleOverflow::Visible &&
+ currentFrame->StyleDisplay()->mOverflowY !=
+ StyleOverflow::Visible));
+ });
+}
+
+// static
+nsRect nsLayoutUtils::GetScrolledRect(nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledFrameOverflowArea,
+ const nsSize& aScrollPortSize,
+ StyleDirection aDirection) {
+ WritingMode wm = aScrolledFrame->GetWritingMode();
+ // Potentially override the frame's direction to use the direction found
+ // by nsHTMLScrollFrame::GetScrolledFrameDir()
+ wm.SetDirectionFromBidiLevel(aDirection == StyleDirection::Rtl
+ ? mozilla::intl::BidiEmbeddingLevel::RTL()
+ : mozilla::intl::BidiEmbeddingLevel::LTR());
+
+ nscoord x1 = aScrolledFrameOverflowArea.x,
+ x2 = aScrolledFrameOverflowArea.XMost(),
+ y1 = aScrolledFrameOverflowArea.y,
+ y2 = aScrolledFrameOverflowArea.YMost();
+
+ const bool isHorizontalWM = !wm.IsVertical();
+ const bool isVerticalWM = wm.IsVertical();
+ bool isInlineFlowFromTopOrLeft = !wm.IsInlineReversed();
+ bool isBlockFlowFromTopOrLeft = isHorizontalWM || wm.IsVerticalLR();
+
+ if (aScrolledFrame->IsFlexContainerFrame()) {
+ // In a flex container, the children flow (and overflow) along the flex
+ // container's main axis and cross axis. These are analogous to the
+ // inline/block axes, and by default they correspond exactly to those axes;
+ // but the flex container's CSS (e.g. flex-direction: column-reverse) may
+ // have swapped and/or reversed them, and we need to account for that here.
+ FlexboxAxisInfo info(aScrolledFrame);
+ if (info.mIsRowOriented) {
+ // The flex container's inline axis is the main axis.
+ isInlineFlowFromTopOrLeft =
+ isInlineFlowFromTopOrLeft == !info.mIsMainAxisReversed;
+ isBlockFlowFromTopOrLeft =
+ isBlockFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
+ } else {
+ // The flex container's block axis is the main axis.
+ isBlockFlowFromTopOrLeft =
+ isBlockFlowFromTopOrLeft == !info.mIsMainAxisReversed;
+ isInlineFlowFromTopOrLeft =
+ isInlineFlowFromTopOrLeft == !info.mIsCrossAxisReversed;
+ }
+ }
+
+ // Clamp the horizontal start-edge (x1 or x2, depending whether the logical
+ // axis that corresponds to horizontal progresses from left-to-right or
+ // right-to-left).
+ if ((isHorizontalWM && isInlineFlowFromTopOrLeft) ||
+ (isVerticalWM && isBlockFlowFromTopOrLeft)) {
+ if (x1 < 0) {
+ x1 = 0;
+ }
+ } else {
+ if (x2 > aScrollPortSize.width) {
+ x2 = aScrollPortSize.width;
+ }
+ // When the scrolled frame chooses a size larger than its available width
+ // (because its padding alone is larger than the available width), we need
+ // to keep the start-edge of the scroll frame anchored to the start-edge of
+ // the scrollport.
+ // When the scrolled frame is RTL, this means moving it in our left-based
+ // coordinate system, so we need to compensate for its extra width here by
+ // effectively repositioning the frame.
+ nscoord extraWidth =
+ std::max(0, aScrolledFrame->GetSize().width - aScrollPortSize.width);
+ x2 += extraWidth;
+ }
+
+ // Similarly, clamp the vertical start-edge (y1 or y2, depending whether the
+ // logical axis that corresponds to vertical progresses from top-to-bottom or
+ // buttom-to-top).
+ if ((isHorizontalWM && isBlockFlowFromTopOrLeft) ||
+ (isVerticalWM && isInlineFlowFromTopOrLeft)) {
+ if (y1 < 0) {
+ y1 = 0;
+ }
+ } else {
+ if (y2 > aScrollPortSize.height) {
+ y2 = aScrollPortSize.height;
+ }
+ nscoord extraHeight =
+ std::max(0, aScrolledFrame->GetSize().height - aScrollPortSize.height);
+ y2 += extraHeight;
+ }
+
+ return nsRect(x1, y1, x2 - x1, y2 - y1);
+}
+
+// static
+bool nsLayoutUtils::HasPseudoStyle(nsIContent* aContent,
+ ComputedStyle* aComputedStyle,
+ PseudoStyleType aPseudoElement,
+ nsPresContext* aPresContext) {
+ MOZ_ASSERT(aPresContext, "Must have a prescontext");
+
+ RefPtr<ComputedStyle> pseudoContext;
+ if (aContent) {
+ pseudoContext = aPresContext->StyleSet()->ProbePseudoElementStyle(
+ *aContent->AsElement(), aPseudoElement, nullptr, aComputedStyle);
+ }
+ return pseudoContext != nullptr;
+}
+
+nsPoint nsLayoutUtils::GetDOMEventCoordinatesRelativeTo(Event* aDOMEvent,
+ nsIFrame* aFrame) {
+ if (!aDOMEvent) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ WidgetEvent* event = aDOMEvent->WidgetEventPtr();
+ if (!event) return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ return GetEventCoordinatesRelativeTo(event, RelativeTo{aFrame});
+}
+
+static bool IsValidCoordinateTypeEvent(const WidgetEvent* aEvent) {
+ if (!aEvent) {
+ return false;
+ }
+ return aEvent->mClass == eMouseEventClass ||
+ aEvent->mClass == eMouseScrollEventClass ||
+ aEvent->mClass == eWheelEventClass ||
+ aEvent->mClass == eDragEventClass ||
+ aEvent->mClass == eSimpleGestureEventClass ||
+ aEvent->mClass == ePointerEventClass ||
+ aEvent->mClass == eGestureNotifyEventClass ||
+ aEvent->mClass == eTouchEventClass ||
+ aEvent->mClass == eQueryContentEventClass;
+}
+
+nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(const WidgetEvent* aEvent,
+ RelativeTo aFrame) {
+ if (!IsValidCoordinateTypeEvent(aEvent)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ return GetEventCoordinatesRelativeTo(aEvent, aEvent->AsGUIEvent()->mRefPoint,
+ aFrame);
+}
+
+nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ const WidgetEvent* aEvent, const LayoutDeviceIntPoint& aPoint,
+ RelativeTo aFrame) {
+ if (!aFrame.mFrame) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsIWidget* widget = aEvent->AsGUIEvent()->mWidget;
+ if (!widget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ return GetEventCoordinatesRelativeTo(widget, aPoint, aFrame);
+}
+
+nsPoint GetEventCoordinatesRelativeTo(nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPoint,
+ RelativeTo aFrame) {
+ const nsIFrame* frame = aFrame.mFrame;
+ if (!frame || !aWidget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsView* view = frame->GetView();
+ if (view) {
+ nsIWidget* frameWidget = view->GetWidget();
+ if (frameWidget && frameWidget == aWidget) {
+ // Special case this cause it happens a lot.
+ // This also fixes bug 664707, events in the extra-special case of select
+ // dropdown popups that are transformed.
+ nsPresContext* presContext = frame->PresContext();
+ nsPoint pt(presContext->DevPixelsToAppUnits(aPoint.x),
+ presContext->DevPixelsToAppUnits(aPoint.y));
+ return pt - view->ViewToWidgetOffset();
+ }
+ }
+
+ /* If we walk up the frame tree and discover that any of the frames are
+ * transformed, we need to do extra work to convert from the global
+ * space to the local space.
+ */
+ const nsIFrame* rootFrame = frame;
+ bool transformFound = false;
+ for (const nsIFrame* f = frame; f;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ if (f->IsTransformed() || ViewportUtils::IsZoomedContentRoot(f)) {
+ transformFound = true;
+ }
+
+ rootFrame = f;
+ }
+
+ nsView* rootView = rootFrame->GetView();
+ if (!rootView) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsPoint widgetToView = nsLayoutUtils::TranslateWidgetToView(
+ rootFrame->PresContext(), aWidget, aPoint, rootView);
+
+ if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ // Convert from root document app units to app units of the document aFrame
+ // is in.
+ int32_t rootAPD = rootFrame->PresContext()->AppUnitsPerDevPixel();
+ int32_t localAPD = frame->PresContext()->AppUnitsPerDevPixel();
+ widgetToView = widgetToView.ScaleToOtherAppUnits(rootAPD, localAPD);
+
+ /* If we encountered a transform, we can't do simple arithmetic to figure
+ * out how to convert back to aFrame's coordinates and must use the CTM.
+ */
+ if (transformFound || frame->IsInSVGTextSubtree()) {
+ return nsLayoutUtils::TransformRootPointToFrame(ViewportType::Visual,
+ aFrame, widgetToView);
+ }
+
+ /* Otherwise, all coordinate systems are translations of one another,
+ * so we can just subtract out the difference.
+ */
+ return widgetToView - frame->GetOffsetToCrossDoc(rootFrame);
+}
+
+nsPoint nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ nsIWidget* aWidget, const LayoutDeviceIntPoint& aPoint, RelativeTo aFrame) {
+ nsPoint result = ::GetEventCoordinatesRelativeTo(aWidget, aPoint, aFrame);
+ if (aFrame.mViewportType == ViewportType::Layout && aFrame.mFrame &&
+ aFrame.mFrame->Type() == LayoutFrameType::Viewport &&
+ aFrame.mFrame->PresContext()->IsRootContentDocumentCrossProcess()) {
+ result = ViewportUtils::VisualToLayout(result, aFrame.mFrame->PresShell());
+ }
+ return result;
+}
+
+nsIFrame* nsLayoutUtils::GetPopupFrameForEventCoordinates(
+ nsPresContext* aRootPresContext, const WidgetEvent* aEvent) {
+ if (!IsValidCoordinateTypeEvent(aEvent)) {
+ return nullptr;
+ }
+
+ const auto* guiEvent = aEvent->AsGUIEvent();
+ return GetPopupFrameForPoint(aRootPresContext, guiEvent->mWidget,
+ guiEvent->mRefPoint);
+}
+
+nsIFrame* nsLayoutUtils::GetPopupFrameForPoint(
+ nsPresContext* aRootPresContext, nsIWidget* aWidget,
+ const mozilla::LayoutDeviceIntPoint& aPoint,
+ GetPopupFrameForPointFlags aFlags /* = GetPopupFrameForPointFlags(0) */) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ if (!pm) {
+ return nullptr;
+ }
+ nsTArray<nsIFrame*> popups;
+ pm->GetVisiblePopups(popups);
+ // Search from top to bottom
+ for (nsIFrame* popup : popups) {
+ if (popup->PresContext()->GetRootPresContext() != aRootPresContext) {
+ continue;
+ }
+ if (!popup->ScrollableOverflowRect().Contains(GetEventCoordinatesRelativeTo(
+ aWidget, aPoint, RelativeTo{popup}))) {
+ continue;
+ }
+ if (aFlags & GetPopupFrameForPointFlags::OnlyReturnFramesWithWidgets) {
+ if (!popup->HasView() || !popup->GetView()->HasWidget()) {
+ continue;
+ }
+ }
+ return popup;
+ }
+ return nullptr;
+}
+
+void nsLayoutUtils::GetContainerAndOffsetAtEvent(PresShell* aPresShell,
+ const WidgetEvent* aEvent,
+ nsIContent** aContainer,
+ int32_t* aOffset) {
+ MOZ_ASSERT(aContainer || aOffset);
+
+ if (aContainer) {
+ *aContainer = nullptr;
+ }
+ if (aOffset) {
+ *aOffset = 0;
+ }
+
+ if (!aPresShell) {
+ return;
+ }
+
+ aPresShell->FlushPendingNotifications(FlushType::Layout);
+
+ RefPtr<nsPresContext> presContext = aPresShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ nsIFrame* targetFrame = presContext->EventStateManager()->GetEventTarget();
+ if (!targetFrame) {
+ return;
+ }
+
+ WidgetEvent* openingEvent = nullptr;
+ // For popupshowing events, redirect via the original mouse event
+ // that triggered the popup to open.
+ if (aEvent->mMessage == eXULPopupShowing) {
+ if (auto* pm = nsXULPopupManager::GetInstance()) {
+ if (Event* openingPopupEvent = pm->GetOpeningPopupEvent()) {
+ openingEvent = openingPopupEvent->WidgetEventPtr();
+ }
+ }
+ }
+
+ nsPoint point = nsLayoutUtils::GetEventCoordinatesRelativeTo(
+ openingEvent ? openingEvent : aEvent, RelativeTo{targetFrame});
+
+ if (aContainer) {
+ // TODO: This result may be useful to change to Selection. However, this
+ // may return improper node (e.g., native anonymous node) for the
+ // Selection. Perhaps, this should take Selection optionally and
+ // if it's specified, needs to check if it's proper for the
+ // Selection.
+ nsCOMPtr<nsIContent> container =
+ targetFrame->GetContentOffsetsFromPoint(point).content;
+ if (container && (!container->ChromeOnlyAccess() ||
+ nsContentUtils::CanAccessNativeAnon())) {
+ container.forget(aContainer);
+ }
+ }
+ if (aOffset) {
+ *aOffset = targetFrame->GetContentOffsetsFromPoint(point).offset;
+ }
+}
+
+void nsLayoutUtils::ConstrainToCoordValues(float& aStart, float& aSize) {
+ MOZ_ASSERT(aSize >= 0);
+
+ // Here we try to make sure that the resulting nsRect will continue to cover
+ // as much of the area that was covered by the original gfx Rect as possible.
+
+ // We clamp the bounds of the rect to {nscoord_MIN,nscoord_MAX} since
+ // nsRect::X/Y() and nsRect::XMost/YMost() can't return values outwith this
+ // range:
+ float end = aStart + aSize;
+ aStart = clamped(aStart, float(nscoord_MIN), float(nscoord_MAX));
+ end = clamped(end, float(nscoord_MIN), float(nscoord_MAX));
+
+ aSize = end - aStart;
+
+ // We must also clamp aSize to {0,nscoord_MAX} since nsRect::Width/Height()
+ // can't return a value greater than nscoord_MAX. If aSize is greater than
+ // nscoord_MAX then we reduce it to nscoord_MAX while keeping the rect
+ // centered:
+ if (MOZ_UNLIKELY(std::isnan(aSize))) {
+ // Can happen if aStart is -inf and aSize is +inf for example.
+ aStart = 0.0f;
+ aSize = float(nscoord_MAX);
+ } else if (aSize > float(nscoord_MAX)) {
+ float excess = aSize - float(nscoord_MAX);
+ excess /= 2;
+ aStart += excess;
+ aSize = float(nscoord_MAX);
+ }
+}
+
+/**
+ * Given a gfxFloat, constrains its value to be between nscoord_MIN and
+ * nscoord_MAX.
+ *
+ * @param aVal The value to constrain (in/out)
+ */
+static void ConstrainToCoordValues(gfxFloat& aVal) {
+ if (aVal <= nscoord_MIN)
+ aVal = nscoord_MIN;
+ else if (aVal >= nscoord_MAX)
+ aVal = nscoord_MAX;
+}
+
+void nsLayoutUtils::ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize) {
+ gfxFloat max = aStart + aSize;
+
+ // Clamp the end points to within nscoord range
+ ::ConstrainToCoordValues(aStart);
+ ::ConstrainToCoordValues(max);
+
+ aSize = max - aStart;
+ // If the width if still greater than the max nscoord, then bring both
+ // endpoints in by the same amount until it fits.
+ if (MOZ_UNLIKELY(std::isnan(aSize))) {
+ // Can happen if aStart is -inf and aSize is +inf for example.
+ aStart = 0.0f;
+ aSize = nscoord_MAX;
+ } else if (aSize > nscoord_MAX) {
+ gfxFloat excess = aSize - nscoord_MAX;
+ excess /= 2;
+
+ aStart += excess;
+ aSize = nscoord_MAX;
+ } else if (aSize < nscoord_MIN) {
+ gfxFloat excess = aSize - nscoord_MIN;
+ excess /= 2;
+
+ aStart -= excess;
+ aSize = nscoord_MIN;
+ }
+}
+
+nsRegion nsLayoutUtils::RoundedRectIntersectRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect) {
+ // rectFullHeight and rectFullWidth together will approximately contain
+ // the total area of the frame minus the rounded corners.
+ nsRect rectFullHeight = aRoundedRect;
+ nscoord xDiff = std::max(aRadii[eCornerTopLeftX], aRadii[eCornerBottomLeftX]);
+ rectFullHeight.x += xDiff;
+ rectFullHeight.width -=
+ std::max(aRadii[eCornerTopRightX], aRadii[eCornerBottomRightX]) + xDiff;
+ nsRect r1;
+ r1.IntersectRect(rectFullHeight, aContainedRect);
+
+ nsRect rectFullWidth = aRoundedRect;
+ nscoord yDiff = std::max(aRadii[eCornerTopLeftY], aRadii[eCornerTopRightY]);
+ rectFullWidth.y += yDiff;
+ rectFullWidth.height -=
+ std::max(aRadii[eCornerBottomLeftY], aRadii[eCornerBottomRightY]) + yDiff;
+ nsRect r2;
+ r2.IntersectRect(rectFullWidth, aContainedRect);
+
+ nsRegion result;
+ result.Or(r1, r2);
+ return result;
+}
+
+nsIntRegion nsLayoutUtils::RoundedRectIntersectIntRect(
+ const nsIntRect& aRoundedRect, const RectCornerRadii& aCornerRadii,
+ const nsIntRect& aContainedRect) {
+ // rectFullHeight and rectFullWidth together will approximately contain
+ // the total area of the frame minus the rounded corners.
+ nsIntRect rectFullHeight = aRoundedRect;
+ uint32_t xDiff =
+ std::max(aCornerRadii.TopLeft().width, aCornerRadii.BottomLeft().width);
+ rectFullHeight.x += xDiff;
+ rectFullHeight.width -= std::max(aCornerRadii.TopRight().width,
+ aCornerRadii.BottomRight().width) +
+ xDiff;
+ nsIntRect r1;
+ r1.IntersectRect(rectFullHeight, aContainedRect);
+
+ nsIntRect rectFullWidth = aRoundedRect;
+ uint32_t yDiff =
+ std::max(aCornerRadii.TopLeft().height, aCornerRadii.TopRight().height);
+ rectFullWidth.y += yDiff;
+ rectFullWidth.height -= std::max(aCornerRadii.BottomLeft().height,
+ aCornerRadii.BottomRight().height) +
+ yDiff;
+ nsIntRect r2;
+ r2.IntersectRect(rectFullWidth, aContainedRect);
+
+ nsIntRegion result;
+ result.Or(r1, r2);
+ return result;
+}
+
+// Helper for RoundedRectIntersectsRect.
+static bool CheckCorner(nscoord aXOffset, nscoord aYOffset, nscoord aXRadius,
+ nscoord aYRadius) {
+ MOZ_ASSERT(aXOffset > 0 && aYOffset > 0,
+ "must not pass nonpositives to CheckCorner");
+ MOZ_ASSERT(aXRadius >= 0 && aYRadius >= 0,
+ "must not pass negatives to CheckCorner");
+
+ // Avoid floating point math unless we're either (1) within the
+ // quarter-ellipse area at the rounded corner or (2) outside the
+ // rounding.
+ if (aXOffset >= aXRadius || aYOffset >= aYRadius) return true;
+
+ // Convert coordinates to a unit circle with (0,0) as the center of
+ // curvature, and see if we're inside the circle or outside.
+ float scaledX = float(aXRadius - aXOffset) / float(aXRadius);
+ float scaledY = float(aYRadius - aYOffset) / float(aYRadius);
+ return scaledX * scaledX + scaledY * scaledY < 1.0f;
+}
+
+bool nsLayoutUtils::RoundedRectIntersectsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aTestRect) {
+ if (!aTestRect.Intersects(aRoundedRect)) return false;
+
+ // distances from this edge of aRoundedRect to opposite edge of aTestRect,
+ // which we know are positive due to the Intersects check above.
+ nsMargin insets;
+ insets.top = aTestRect.YMost() - aRoundedRect.y;
+ insets.right = aRoundedRect.XMost() - aTestRect.x;
+ insets.bottom = aRoundedRect.YMost() - aTestRect.y;
+ insets.left = aTestRect.XMost() - aRoundedRect.x;
+
+ // Check whether the bottom-right corner of aTestRect is inside the
+ // top left corner of aBounds when rounded by aRadii, etc. If any
+ // corner is not, then fail; otherwise succeed.
+ return CheckCorner(insets.left, insets.top, aRadii[eCornerTopLeftX],
+ aRadii[eCornerTopLeftY]) &&
+ CheckCorner(insets.right, insets.top, aRadii[eCornerTopRightX],
+ aRadii[eCornerTopRightY]) &&
+ CheckCorner(insets.right, insets.bottom, aRadii[eCornerBottomRightX],
+ aRadii[eCornerBottomRightY]) &&
+ CheckCorner(insets.left, insets.bottom, aRadii[eCornerBottomLeftX],
+ aRadii[eCornerBottomLeftY]);
+}
+
+nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
+ const Matrix4x4& aMatrix,
+ float aFactor) {
+ RectDouble image =
+ RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.y, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.width, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.height, aFactor));
+
+ RectDouble maxBounds = RectDouble(
+ double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
+ double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
+
+ image = aMatrix.TransformAndClipBounds(image, maxBounds);
+
+ return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
+}
+
+nsRect nsLayoutUtils::MatrixTransformRect(const nsRect& aBounds,
+ const Matrix4x4Flagged& aMatrix,
+ float aFactor) {
+ RectDouble image =
+ RectDouble(NSAppUnitsToDoublePixels(aBounds.x, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.y, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.width, aFactor),
+ NSAppUnitsToDoublePixels(aBounds.height, aFactor));
+
+ RectDouble maxBounds = RectDouble(
+ double(nscoord_MIN) / aFactor * 0.5, double(nscoord_MIN) / aFactor * 0.5,
+ double(nscoord_MAX) / aFactor, double(nscoord_MAX) / aFactor);
+
+ image = aMatrix.TransformAndClipBounds(image, maxBounds);
+
+ return RoundGfxRectToAppRect(ThebesRect(image), aFactor);
+}
+
+nsPoint nsLayoutUtils::MatrixTransformPoint(const nsPoint& aPoint,
+ const Matrix4x4& aMatrix,
+ float aFactor) {
+ gfxPoint image = gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor),
+ NSAppUnitsToFloatPixels(aPoint.y, aFactor));
+ image = aMatrix.TransformPoint(image);
+ return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor),
+ NSFloatPixelsToAppUnits(float(image.y), aFactor));
+}
+
+void nsLayoutUtils::PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin,
+ float aAppUnitsPerPixel, bool aRounded) {
+ Point3D gfxOrigin =
+ Point3D(NSAppUnitsToFloatPixels(aOrigin.x, aAppUnitsPerPixel),
+ NSAppUnitsToFloatPixels(aOrigin.y, aAppUnitsPerPixel), 0.0f);
+ if (aRounded) {
+ gfxOrigin.x = NS_round(gfxOrigin.x);
+ gfxOrigin.y = NS_round(gfxOrigin.y);
+ }
+ aTransform.PostTranslate(gfxOrigin);
+}
+
+bool nsLayoutUtils::ShouldSnapToGrid(const nsIFrame* aFrame) {
+ return !aFrame || !aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
+ aFrame->IsSVGOuterSVGAnonChildFrame();
+}
+
+Matrix4x4Flagged nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo aFrame, RelativeTo aAncestor, uint32_t aFlags,
+ nsIFrame** aOutAncestor) {
+ nsIFrame* parent;
+ Matrix4x4Flagged ctm;
+ // Make sure we don't get an invalid combination of source and destination
+ // RelativeTo values.
+ MOZ_ASSERT(!(aFrame.mViewportType == ViewportType::Visual &&
+ aAncestor.mViewportType == ViewportType::Layout));
+ if (aFrame == aAncestor) {
+ return ctm;
+ }
+ ctm = aFrame.mFrame->GetTransformMatrix(aFrame.mViewportType, aAncestor,
+ &parent, aFlags);
+ if (!aFrame.mFrame->Combines3DTransformWithAncestors()) {
+ ctm.ProjectTo2D();
+ }
+ while (parent && parent != aAncestor.mFrame &&
+ (!(aFlags & nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT) ||
+ (!parent->IsStackingContext() &&
+ !DisplayPortUtils::FrameHasDisplayPort(parent)))) {
+ nsIFrame* cur = parent;
+ ctm = ctm * cur->GetTransformMatrix(aFrame.mViewportType, aAncestor,
+ &parent, aFlags);
+ if (!cur->Combines3DTransformWithAncestors()) {
+ ctm.ProjectTo2D();
+ }
+ }
+ if (aOutAncestor) {
+ *aOutAncestor = parent;
+ }
+ return ctm;
+}
+
+MatrixScales nsLayoutUtils::GetTransformToAncestorScale(
+ const nsIFrame* aFrame) {
+ Matrix4x4Flagged transform = GetTransformToAncestor(
+ RelativeTo{aFrame},
+ RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
+ Matrix transform2D;
+ if (transform.CanDraw2D(&transform2D)) {
+ return ThebesMatrix(transform2D).ScaleFactors().ConvertTo<float>();
+ }
+ return MatrixScales();
+}
+
+static Matrix4x4Flagged GetTransformToAncestorExcludingAnimated(
+ nsIFrame* aFrame, const nsIFrame* aAncestor) {
+ nsIFrame* parent;
+ Matrix4x4Flagged ctm;
+ if (aFrame == aAncestor) {
+ return ctm;
+ }
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(aFrame)) {
+ return ctm;
+ }
+ ctm = aFrame->GetTransformMatrix(ViewportType::Layout, RelativeTo{aAncestor},
+ &parent);
+ while (parent && parent != aAncestor) {
+ if (ActiveLayerTracker::IsScaleSubjectToAnimation(parent)) {
+ return Matrix4x4Flagged();
+ }
+ if (!parent->Extend3DContext()) {
+ ctm.ProjectTo2D();
+ }
+ ctm = ctm * parent->GetTransformMatrix(ViewportType::Layout,
+ RelativeTo{aAncestor}, &parent);
+ }
+ return ctm;
+}
+
+MatrixScales nsLayoutUtils::GetTransformToAncestorScaleExcludingAnimated(
+ nsIFrame* aFrame) {
+ Matrix4x4Flagged transform = GetTransformToAncestorExcludingAnimated(
+ aFrame, nsLayoutUtils::GetDisplayRootFrame(aFrame));
+ Matrix transform2D;
+ if (transform.Is2D(&transform2D)) {
+ return ThebesMatrix(transform2D).ScaleFactors().ConvertTo<float>();
+ }
+ return MatrixScales();
+}
+
+const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrame(
+ const nsIFrame* aFrame1, const nsIFrame* aFrame2) {
+ AutoTArray<const nsIFrame*, 100> ancestors1;
+ AutoTArray<const nsIFrame*, 100> ancestors2;
+ const nsIFrame* commonAncestor = nullptr;
+ if (aFrame1->PresContext() == aFrame2->PresContext()) {
+ commonAncestor = aFrame1->PresShell()->GetRootFrame();
+ }
+ for (const nsIFrame* f = aFrame1; f != commonAncestor;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ ancestors1.AppendElement(f);
+ }
+ for (const nsIFrame* f = aFrame2; f != commonAncestor;
+ f = nsLayoutUtils::GetCrossDocParentFrameInProcess(f)) {
+ ancestors2.AppendElement(f);
+ }
+ uint32_t minLengths = std::min(ancestors1.Length(), ancestors2.Length());
+ for (uint32_t i = 1; i <= minLengths; ++i) {
+ if (ancestors1[ancestors1.Length() - i] ==
+ ancestors2[ancestors2.Length() - i]) {
+ commonAncestor = ancestors1[ancestors1.Length() - i];
+ } else {
+ break;
+ }
+ }
+ return commonAncestor;
+}
+
+const nsIFrame* nsLayoutUtils::FindNearestCommonAncestorFrameWithinBlock(
+ const nsTextFrame* aFrame1, const nsTextFrame* aFrame2) {
+ MOZ_ASSERT(aFrame1);
+ MOZ_ASSERT(aFrame2);
+
+ const nsIFrame* f1 = aFrame1;
+ const nsIFrame* f2 = aFrame2;
+
+ int n1 = 1;
+ int n2 = 1;
+
+ for (auto f = f1->GetParent();;) {
+ NS_ASSERTION(f, "All text frames should have a block ancestor");
+ if (!f) {
+ return nullptr;
+ }
+ if (f->IsBlockFrameOrSubclass()) {
+ break;
+ }
+ ++n1;
+ f = f->GetParent();
+ }
+
+ for (auto f = f2->GetParent();;) {
+ NS_ASSERTION(f, "All text frames should have a block ancestor");
+ if (!f) {
+ return nullptr;
+ }
+ if (f->IsBlockFrameOrSubclass()) {
+ break;
+ }
+ ++n2;
+ f = f->GetParent();
+ }
+
+ if (n1 > n2) {
+ std::swap(n1, n2);
+ std::swap(f1, f2);
+ }
+
+ while (n2 > n1) {
+ f2 = f2->GetParent();
+ --n2;
+ }
+
+ while (n2 >= 0) {
+ if (f1 == f2) {
+ return f1;
+ }
+ f1 = f1->GetParent();
+ f2 = f2->GetParent();
+ --n2;
+ }
+
+ return nullptr;
+}
+
+bool nsLayoutUtils::AuthorSpecifiedBorderBackgroundDisablesTheming(
+ StyleAppearance aAppearance) {
+ return aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea ||
+ aAppearance == StyleAppearance::Listbox ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton;
+}
+
+static SVGTextFrame* GetContainingSVGTextFrame(const nsIFrame* aFrame) {
+ if (!aFrame->IsInSVGTextSubtree()) {
+ return nullptr;
+ }
+
+ return static_cast<SVGTextFrame*>(nsLayoutUtils::GetClosestFrameOfType(
+ aFrame->GetParent(), LayoutFrameType::SVGText));
+}
+
+static bool TransformGfxPointFromAncestor(RelativeTo aFrame,
+ const Point& aPoint,
+ RelativeTo aAncestor,
+ Maybe<Matrix4x4Flagged>& aMatrixCache,
+ Point* aOut) {
+ SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame);
+
+ if (!aMatrixCache) {
+ auto matrix = nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{text ? text : aFrame.mFrame, aFrame.mViewportType},
+ aAncestor);
+ if (matrix.IsSingular()) {
+ return false;
+ }
+ matrix.Invert();
+ aMatrixCache.emplace(matrix);
+ }
+
+ const Matrix4x4Flagged& ctm = *aMatrixCache;
+ Point4D point = ctm.ProjectPoint(aPoint);
+ if (!point.HasPositiveWCoord()) {
+ return false;
+ }
+
+ *aOut = point.As2DPoint();
+
+ if (text) {
+ *aOut = text->TransformFramePointToTextChild(*aOut, aFrame.mFrame);
+ }
+
+ return true;
+}
+
+static Point TransformGfxPointToAncestor(
+ RelativeTo aFrame, const Point& aPoint, RelativeTo aAncestor,
+ Maybe<Matrix4x4Flagged>& aMatrixCache) {
+ if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
+ Point result =
+ text->TransformFramePointFromTextChild(aPoint, aFrame.mFrame);
+ return TransformGfxPointToAncestor(RelativeTo{text}, result, aAncestor,
+ aMatrixCache);
+ }
+ if (!aMatrixCache) {
+ aMatrixCache.emplace(
+ nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor));
+ }
+ return aMatrixCache->ProjectPoint(aPoint).As2DPoint();
+}
+
+static Rect TransformGfxRectToAncestor(
+ RelativeTo aFrame, const Rect& aRect, RelativeTo aAncestor,
+ bool* aPreservesAxisAlignedRectangles = nullptr,
+ Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr,
+ bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false,
+ nsIFrame** aOutAncestor = nullptr) {
+ Rect result;
+ Matrix4x4Flagged ctm;
+ if (SVGTextFrame* text = GetContainingSVGTextFrame(aFrame.mFrame)) {
+ result = text->TransformFrameRectFromTextChild(aRect, aFrame.mFrame);
+
+ result = TransformGfxRectToAncestor(
+ RelativeTo{text}, result, aAncestor, nullptr, aMatrixCache,
+ aStopAtStackingContextAndDisplayPortAndOOFFrame, aOutAncestor);
+ if (aPreservesAxisAlignedRectangles) {
+ // TransformFrameRectFromTextChild could involve any kind of transform, we
+ // could drill down into it to get an answer out of it but we don't yet.
+ *aPreservesAxisAlignedRectangles = false;
+ }
+ return result;
+ }
+ if (aMatrixCache && *aMatrixCache) {
+ // We are given a matrix to use, so use it
+ ctm = aMatrixCache->value();
+ } else {
+ // Else, compute it
+ uint32_t flags = 0;
+ if (aStopAtStackingContextAndDisplayPortAndOOFFrame) {
+ flags |= nsIFrame::STOP_AT_STACKING_CONTEXT_AND_DISPLAY_PORT;
+ }
+ ctm = nsLayoutUtils::GetTransformToAncestor(aFrame, aAncestor, flags,
+ aOutAncestor);
+ if (aMatrixCache) {
+ // and put it in the cache, if provided
+ *aMatrixCache = Some(ctm);
+ }
+ }
+ // Fill out the axis-alignment flag
+ if (aPreservesAxisAlignedRectangles) {
+ // TransformFrameRectFromTextChild could involve any kind of transform, we
+ // could drill down into it to get an answer out of it but we don't yet.
+ Matrix matrix2d;
+ *aPreservesAxisAlignedRectangles =
+ ctm.Is2D(&matrix2d) && matrix2d.PreservesAxisAlignedRectangles();
+ }
+ const nsIFrame* ancestor = aOutAncestor ? *aOutAncestor : aAncestor.mFrame;
+ float factor = ancestor->PresContext()->AppUnitsPerDevPixel();
+ Rect maxBounds =
+ Rect(float(nscoord_MIN) / factor * 0.5, float(nscoord_MIN) / factor * 0.5,
+ float(nscoord_MAX) / factor, float(nscoord_MAX) / factor);
+ return ctm.TransformAndClipBounds(aRect, maxBounds);
+}
+
+nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoints(
+ RelativeTo aFromFrame, RelativeTo aToFrame, uint32_t aPointCount,
+ CSSPoint* aPoints) {
+ // Conceptually, {ViewportFrame, Visual} is an ancestor of
+ // {ViewportFrame, Layout}, so factor that into the nearest ancestor
+ // computation.
+ RelativeTo nearestCommonAncestor{
+ FindNearestCommonAncestorFrame(aFromFrame.mFrame, aToFrame.mFrame),
+ aFromFrame.mViewportType == ViewportType::Visual ||
+ aToFrame.mViewportType == ViewportType::Visual
+ ? ViewportType::Visual
+ : ViewportType::Layout};
+ if (!nearestCommonAncestor.mFrame) {
+ return NO_COMMON_ANCESTOR;
+ }
+ CSSToLayoutDeviceScale devPixelsPerCSSPixelFromFrame =
+ aFromFrame.mFrame->PresContext()->CSSToDevPixelScale();
+ CSSToLayoutDeviceScale devPixelsPerCSSPixelToFrame =
+ aToFrame.mFrame->PresContext()->CSSToDevPixelScale();
+ Maybe<Matrix4x4Flagged> cacheTo;
+ Maybe<Matrix4x4Flagged> cacheFrom;
+ for (uint32_t i = 0; i < aPointCount; ++i) {
+ LayoutDevicePoint devPixels = aPoints[i] * devPixelsPerCSSPixelFromFrame;
+ // What should the behaviour be if some of the points aren't invertible
+ // and others are? Just assume all points are for now.
+ Point toDevPixels =
+ TransformGfxPointToAncestor(aFromFrame, Point(devPixels.x, devPixels.y),
+ nearestCommonAncestor, cacheTo);
+ Point result;
+ if (!TransformGfxPointFromAncestor(
+ aToFrame, toDevPixels, nearestCommonAncestor, cacheFrom, &result)) {
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ // Divide here so that when the devPixelsPerCSSPixels are the same, we get
+ // the correct answer instead of some inaccuracy multiplying a number by its
+ // reciprocal.
+ aPoints[i] =
+ LayoutDevicePoint(result.x, result.y) / devPixelsPerCSSPixelToFrame;
+ }
+ return TRANSFORM_SUCCEEDED;
+}
+
+nsLayoutUtils::TransformResult nsLayoutUtils::TransformPoint(
+ RelativeTo aFromFrame, RelativeTo aToFrame, nsPoint& aPoint) {
+ CSSPoint point = CSSPoint::FromAppUnits(aPoint);
+ auto result = TransformPoints(aFromFrame, aToFrame, 1, &point);
+ if (result == TRANSFORM_SUCCEEDED) {
+ aPoint = CSSPoint::ToAppUnits(point);
+ }
+ return result;
+}
+
+nsLayoutUtils::TransformResult nsLayoutUtils::TransformRect(
+ const nsIFrame* aFromFrame, const nsIFrame* aToFrame, nsRect& aRect) {
+ const nsIFrame* nearestCommonAncestor =
+ FindNearestCommonAncestorFrame(aFromFrame, aToFrame);
+ if (!nearestCommonAncestor) {
+ return NO_COMMON_ANCESTOR;
+ }
+ Matrix4x4Flagged downToDest = GetTransformToAncestor(
+ RelativeTo{aToFrame}, RelativeTo{nearestCommonAncestor});
+ if (downToDest.IsSingular()) {
+ return NONINVERTIBLE_TRANSFORM;
+ }
+ downToDest.Invert();
+ aRect = TransformFrameRectToAncestor(aFromFrame, aRect,
+ RelativeTo{nearestCommonAncestor});
+
+ float devPixelsPerAppUnitFromFrame =
+ 1.0f / nearestCommonAncestor->PresContext()->AppUnitsPerDevPixel();
+ float devPixelsPerAppUnitToFrame =
+ 1.0f / aToFrame->PresContext()->AppUnitsPerDevPixel();
+ gfx::Rect toDevPixels = downToDest.ProjectRectBounds(
+ gfx::Rect(aRect.x * devPixelsPerAppUnitFromFrame,
+ aRect.y * devPixelsPerAppUnitFromFrame,
+ aRect.width * devPixelsPerAppUnitFromFrame,
+ aRect.height * devPixelsPerAppUnitFromFrame),
+ Rect(-std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
+ 0.5f,
+ -std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame *
+ 0.5f,
+ std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame,
+ std::numeric_limits<Float>::max() * devPixelsPerAppUnitFromFrame));
+ aRect.x = NSToCoordRoundWithClamp(toDevPixels.x / devPixelsPerAppUnitToFrame);
+ aRect.y = NSToCoordRoundWithClamp(toDevPixels.y / devPixelsPerAppUnitToFrame);
+ aRect.width =
+ NSToCoordRoundWithClamp(toDevPixels.width / devPixelsPerAppUnitToFrame);
+ aRect.height =
+ NSToCoordRoundWithClamp(toDevPixels.height / devPixelsPerAppUnitToFrame);
+ return TRANSFORM_SUCCEEDED;
+}
+
+nsRect nsLayoutUtils::GetRectRelativeToFrame(Element* aElement,
+ nsIFrame* aFrame) {
+ if (!aElement || !aFrame) {
+ return nsRect();
+ }
+
+ nsIFrame* frame = aElement->GetPrimaryFrame();
+ if (!frame) {
+ return nsRect();
+ }
+
+ nsRect rect = frame->GetRectRelativeToSelf();
+ nsLayoutUtils::TransformResult rv =
+ nsLayoutUtils::TransformRect(frame, aFrame, rect);
+ if (rv != nsLayoutUtils::TRANSFORM_SUCCEEDED) {
+ return nsRect();
+ }
+
+ return rect;
+}
+
+bool nsLayoutUtils::ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
+ nscoord aInflateSize) {
+ nsRect rect = aRect;
+ rect.Inflate(aInflateSize);
+ return rect.Contains(aPoint);
+}
+
+nsRect nsLayoutUtils::ClampRectToScrollFrames(nsIFrame* aFrame,
+ const nsRect& aRect) {
+ nsIFrame* closestScrollFrame =
+ nsLayoutUtils::GetClosestFrameOfType(aFrame, LayoutFrameType::Scroll);
+
+ nsRect resultRect = aRect;
+
+ while (closestScrollFrame) {
+ nsIScrollableFrame* sf = do_QueryFrame(closestScrollFrame);
+
+ nsRect scrollPortRect = sf->GetScrollPortRect();
+ nsLayoutUtils::TransformRect(closestScrollFrame, aFrame, scrollPortRect);
+
+ resultRect = resultRect.Intersect(scrollPortRect);
+
+ // Check whether aRect is visible in the scroll frame or not.
+ if (resultRect.IsEmpty()) {
+ break;
+ }
+
+ // Get next ancestor scroll frame.
+ closestScrollFrame = nsLayoutUtils::GetClosestFrameOfType(
+ closestScrollFrame->GetParent(), LayoutFrameType::Scroll);
+ }
+
+ return resultRect;
+}
+
+nsPoint nsLayoutUtils::TransformAncestorPointToFrame(RelativeTo aFrame,
+ const nsPoint& aPoint,
+ RelativeTo aAncestor) {
+ float factor = aFrame.mFrame->PresContext()->AppUnitsPerDevPixel();
+ Point result(NSAppUnitsToFloatPixels(aPoint.x, factor),
+ NSAppUnitsToFloatPixels(aPoint.y, factor));
+
+ Maybe<Matrix4x4Flagged> matrixCache;
+ if (!TransformGfxPointFromAncestor(aFrame, result, aAncestor, matrixCache,
+ &result)) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ return nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
+ NSFloatPixelsToAppUnits(float(result.y), factor));
+}
+
+nsRect nsLayoutUtils::TransformFrameRectToAncestor(
+ const nsIFrame* aFrame, const nsRect& aRect, RelativeTo aAncestor,
+ bool* aPreservesAxisAlignedRectangles /* = nullptr */,
+ Maybe<Matrix4x4Flagged>* aMatrixCache /* = nullptr */,
+ bool aStopAtStackingContextAndDisplayPortAndOOFFrame /* = false */,
+ nsIFrame** aOutAncestor /* = nullptr */) {
+ MOZ_ASSERT(IsAncestorFrameCrossDocInProcess(aAncestor.mFrame, aFrame),
+ "Fix the caller");
+ float srcAppUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+ Rect result(NSAppUnitsToFloatPixels(aRect.x, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.y, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.width, srcAppUnitsPerDevPixel),
+ NSAppUnitsToFloatPixels(aRect.height, srcAppUnitsPerDevPixel));
+ result = TransformGfxRectToAncestor(
+ RelativeTo{aFrame}, result, aAncestor, aPreservesAxisAlignedRectangles,
+ aMatrixCache, aStopAtStackingContextAndDisplayPortAndOOFFrame,
+ aOutAncestor);
+
+ float destAppUnitsPerDevPixel =
+ aAncestor.mFrame->PresContext()->AppUnitsPerDevPixel();
+ return nsRect(
+ NSFloatPixelsToAppUnits(float(result.x), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.y), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.width), destAppUnitsPerDevPixel),
+ NSFloatPixelsToAppUnits(float(result.height), destAppUnitsPerDevPixel));
+}
+
+static LayoutDeviceIntPoint GetWidgetOffset(nsIWidget* aWidget,
+ nsIWidget*& aRootWidget) {
+ LayoutDeviceIntPoint offset(0, 0);
+ while (aWidget->GetWindowType() == widget::WindowType::Child) {
+ nsIWidget* parent = aWidget->GetParent();
+ if (!parent) {
+ break;
+ }
+ LayoutDeviceIntRect bounds = aWidget->GetBounds();
+ offset += bounds.TopLeft();
+ aWidget = parent;
+ }
+ aRootWidget = aWidget;
+ return offset;
+}
+
+LayoutDeviceIntPoint nsLayoutUtils::WidgetToWidgetOffset(nsIWidget* aFrom,
+ nsIWidget* aTo) {
+ nsIWidget* fromRoot;
+ LayoutDeviceIntPoint fromOffset = GetWidgetOffset(aFrom, fromRoot);
+ nsIWidget* toRoot;
+ LayoutDeviceIntPoint toOffset = GetWidgetOffset(aTo, toRoot);
+
+ if (fromRoot != toRoot) {
+ fromOffset = aFrom->WidgetToScreenOffset();
+ toOffset = aTo->WidgetToScreenOffset();
+ }
+ return fromOffset - toOffset;
+}
+
+nsPoint nsLayoutUtils::TranslateWidgetToView(nsPresContext* aPresContext,
+ nsIWidget* aWidget,
+ const LayoutDeviceIntPoint& aPt,
+ nsView* aView) {
+ nsPoint viewOffset;
+ nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
+ if (!viewWidget) {
+ return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ LayoutDeviceIntPoint widgetPoint =
+ aPt + WidgetToWidgetOffset(aWidget, viewWidget);
+ nsPoint widgetAppUnits(aPresContext->DevPixelsToAppUnits(widgetPoint.x),
+ aPresContext->DevPixelsToAppUnits(widgetPoint.y));
+ return widgetAppUnits - viewOffset;
+}
+
+LayoutDeviceIntPoint nsLayoutUtils::TranslateViewToWidget(
+ nsPresContext* aPresContext, nsView* aView, nsPoint aPt,
+ ViewportType aViewportType, nsIWidget* aWidget) {
+ nsPoint viewOffset;
+ nsIWidget* viewWidget = aView->GetNearestWidget(&viewOffset);
+ if (!viewWidget) {
+ return LayoutDeviceIntPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
+ }
+
+ nsPoint pt = (aPt + viewOffset);
+ // The target coordinates are visual, so perform a layout-to-visual
+ // conversion if the incoming coordinates are layout.
+ if (aViewportType == ViewportType::Layout && aPresContext->GetPresShell()) {
+ pt = ViewportUtils::LayoutToVisual(pt, aPresContext->GetPresShell());
+ }
+ LayoutDeviceIntPoint relativeToViewWidget(
+ aPresContext->AppUnitsToDevPixels(pt.x),
+ aPresContext->AppUnitsToDevPixels(pt.y));
+ return relativeToViewWidget + WidgetToWidgetOffset(viewWidget, aWidget);
+}
+
+StyleClear nsLayoutUtils::CombineClearType(StyleClear aOrigClearType,
+ StyleClear aNewClearType) {
+ StyleClear clearType = aOrigClearType;
+ switch (clearType) {
+ case StyleClear::Left:
+ if (StyleClear::Right == aNewClearType ||
+ StyleClear::Both == aNewClearType) {
+ clearType = StyleClear::Both;
+ }
+ break;
+ case StyleClear::Right:
+ if (StyleClear::Left == aNewClearType ||
+ StyleClear::Both == aNewClearType) {
+ clearType = StyleClear::Both;
+ }
+ break;
+ case StyleClear::None:
+ if (StyleClear::Left == aNewClearType ||
+ StyleClear::Right == aNewClearType ||
+ StyleClear::Both == aNewClearType) {
+ clearType = aNewClearType;
+ }
+ break;
+ case StyleClear::Both:
+ // Do nothing.
+ break;
+ }
+ return clearType;
+}
+
+#ifdef MOZ_DUMP_PAINTING
+# include <stdio.h>
+
+static bool gDumpEventList = false;
+
+// nsLayoutUtils::PaintFrame() can call itself recursively, so rather than
+// maintaining a single paint count, we need a stack.
+StaticAutoPtr<nsTArray<int>> gPaintCountStack;
+
+struct AutoNestedPaintCount {
+ AutoNestedPaintCount() { gPaintCountStack->AppendElement(0); }
+ ~AutoNestedPaintCount() { gPaintCountStack->RemoveLastElement(); }
+};
+
+#endif
+
+nsIFrame* nsLayoutUtils::GetFrameForPoint(
+ RelativeTo aRelativeTo, nsPoint aPt, const FrameForPointOptions& aOptions) {
+ AUTO_PROFILER_LABEL("nsLayoutUtils::GetFrameForPoint", LAYOUT);
+
+ nsresult rv;
+ AutoTArray<nsIFrame*, 8> outFrames;
+ rv = GetFramesForArea(aRelativeTo, nsRect(aPt, nsSize(1, 1)), outFrames,
+ aOptions);
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ return outFrames.SafeElementAt(0);
+}
+
+nsresult nsLayoutUtils::GetFramesForArea(RelativeTo aRelativeTo,
+ const nsRect& aRect,
+ nsTArray<nsIFrame*>& aOutFrames,
+ const FrameForPointOptions& aOptions) {
+ AUTO_PROFILER_LABEL("nsLayoutUtils::GetFramesForArea", LAYOUT);
+
+ nsIFrame* frame = const_cast<nsIFrame*>(aRelativeTo.mFrame);
+
+ nsDisplayListBuilder builder(frame, nsDisplayListBuilderMode::EventDelivery,
+ false);
+ builder.BeginFrame();
+ nsDisplayList list(&builder);
+
+ if (aOptions.mBits.contains(FrameForPointOption::IgnorePaintSuppression)) {
+ builder.IgnorePaintSuppression();
+ }
+ if (aOptions.mBits.contains(FrameForPointOption::IgnoreRootScrollFrame)) {
+ nsIFrame* rootScrollFrame = frame->PresShell()->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ builder.SetIgnoreScrollFrame(rootScrollFrame);
+ }
+ }
+ if (aRelativeTo.mViewportType == ViewportType::Layout) {
+ builder.SetIsRelativeToLayoutViewport();
+ }
+ if (aOptions.mBits.contains(FrameForPointOption::IgnoreCrossDoc)) {
+ builder.SetDescendIntoSubdocuments(false);
+ }
+
+ if (aOptions.mBits.contains(FrameForPointOption::OnlyVisible)) {
+ builder.SetHitTestIsForVisibility(aOptions.mVisibleThreshold);
+ }
+
+ builder.EnterPresShell(frame);
+
+ builder.SetVisibleRect(aRect);
+ builder.SetDirtyRect(aRect);
+
+ frame->BuildDisplayListForStackingContext(&builder, &list);
+ builder.LeavePresShell(frame, nullptr);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gDumpEventList) {
+ fprintf_stderr(stderr, "Event handling --- (%d,%d):\n", aRect.x, aRect.y);
+
+ std::stringstream ss;
+ nsIFrame::PrintDisplayList(&builder, list, ss);
+ print_stderr(ss);
+ }
+#endif
+
+ nsDisplayItem::HitTestState hitTestState;
+ list.HitTest(&builder, aRect, &hitTestState, &aOutFrames);
+ list.DeleteAll(&builder);
+ builder.EndFrame();
+ return NS_OK;
+}
+
+mozilla::ParentLayerToScreenScale2D
+nsLayoutUtils::GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ const nsIFrame* aFrame) {
+ ParentLayerToScreenScale2D transformToAncestorScale =
+ ViewAs<ParentLayerToScreenScale2D>(
+ nsLayoutUtils::GetTransformToAncestorScale(aFrame));
+
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(aFrame->PresShell())) {
+ transformToAncestorScale =
+ ViewTargetAs<ParentLayerPixel>(
+ transformToAncestorScale,
+ PixelCastJustification::PropagatingToChildProcess) *
+ browserChild->GetEffectsInfo().mTransformToAncestorScale;
+ }
+
+ return transformToAncestorScale;
+}
+
+// aScrollFrameAsScrollable must be non-nullptr and queryable to an nsIFrame
+FrameMetrics nsLayoutUtils::CalculateBasicFrameMetrics(
+ nsIScrollableFrame* aScrollFrame) {
+ nsIFrame* frame = do_QueryFrame(aScrollFrame);
+ MOZ_ASSERT(frame);
+
+ // Calculate the metrics necessary for calculating the displayport.
+ // This code has a lot in common with the code in ComputeFrameMetrics();
+ // we may want to refactor this at some point.
+ FrameMetrics metrics;
+ nsPresContext* presContext = frame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ CSSToLayoutDeviceScale deviceScale = presContext->CSSToDevPixelScale();
+ float resolution = 1.0f;
+ bool isRcdRsf = aScrollFrame->IsRootScrollFrameOfDocument() &&
+ presContext->IsRootContentDocumentCrossProcess();
+ metrics.SetIsRootContent(isRcdRsf);
+ if (isRcdRsf) {
+ // Only the root content document's root scrollable frame should pick up
+ // the presShell's resolution. All the other frames are 1.0.
+ resolution = presShell->GetResolution();
+ }
+ LayoutDeviceToLayerScale cumulativeResolution(
+ LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
+
+ LayerToParentLayerScale layerToParentLayerScale(1.0f);
+ metrics.SetDevPixelsPerCSSPixel(deviceScale);
+ metrics.SetPresShellResolution(resolution);
+
+ metrics.SetTransformToAncestorScale(
+ GetTransformToAncestorScaleCrossProcessForFrameMetrics(frame));
+ metrics.SetCumulativeResolution(cumulativeResolution);
+ metrics.SetZoom(deviceScale * cumulativeResolution * layerToParentLayerScale);
+
+ // Only the size of the composition bounds is relevant to the
+ // displayport calculation, not its origin.
+ nsSize compositionSize =
+ nsLayoutUtils::CalculateCompositionSizeForFrame(frame);
+ LayoutDeviceToParentLayerScale compBoundsScale;
+ if (frame == presShell->GetRootScrollFrame() &&
+ presContext->IsRootContentDocumentCrossProcess()) {
+ if (presContext->GetParentPresContext()) {
+ float res = presContext->GetParentPresContext()
+ ->PresShell()
+ ->GetCumulativeResolution();
+ compBoundsScale = LayoutDeviceToParentLayerScale(res);
+ }
+ } else {
+ compBoundsScale = cumulativeResolution * layerToParentLayerScale;
+ }
+ metrics.SetCompositionBounds(
+ LayoutDeviceRect::FromAppUnits(nsRect(nsPoint(0, 0), compositionSize),
+ presContext->AppUnitsPerDevPixel()) *
+ compBoundsScale);
+
+ metrics.SetBoundingCompositionSize(
+ nsLayoutUtils::CalculateBoundingCompositionSize(frame, false, metrics));
+
+ metrics.SetLayoutViewport(
+ CSSRect::FromAppUnits(nsRect(aScrollFrame->GetScrollPosition(),
+ aScrollFrame->GetScrollPortRect().Size())));
+ metrics.SetVisualScrollOffset(
+ isRcdRsf ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
+ : metrics.GetLayoutViewport().TopLeft());
+
+ metrics.SetScrollableRect(CSSRect::FromAppUnits(
+ nsLayoutUtils::CalculateScrollableRectForFrame(aScrollFrame, nullptr)));
+
+ return metrics;
+}
+
+nsIScrollableFrame* nsLayoutUtils::GetAsyncScrollableAncestorFrame(
+ nsIFrame* aTarget) {
+ uint32_t flags = nsLayoutUtils::SCROLLABLE_ALWAYS_MATCH_ROOT |
+ nsLayoutUtils::SCROLLABLE_ONLY_ASYNC_SCROLLABLE |
+ nsLayoutUtils::SCROLLABLE_FIXEDPOS_FINDS_ROOT;
+ return nsLayoutUtils::GetNearestScrollableFrame(aTarget, flags);
+}
+
+void nsLayoutUtils::AddExtraBackgroundItems(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ nsIFrame* aFrame,
+ const nsRect& aCanvasArea,
+ const nsRegion& aVisibleRegion,
+ nscolor aBackstop) {
+ if (aFrame->IsPageFrame()) {
+ // For printing, this function is first called on an nsPageFrame, which
+ // creates a display list with a PageContent item. The PageContent item's
+ // paint function calls this function on the nsPageFrame's child which is an
+ // nsPageContentFrame. We only want to add the canvas background color item
+ // once, for the nsPageContentFrame.
+ return;
+ }
+ // 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.
+ nsRect canvasArea = aVisibleRegion.GetBounds();
+ canvasArea.IntersectRect(aCanvasArea, canvasArea);
+ nsDisplayListBuilder::AutoBuildingDisplayList buildingDisplayList(
+ aBuilder, aFrame, canvasArea, canvasArea);
+ aFrame->PresShell()->AddCanvasBackgroundColorItem(aBuilder, aList, aFrame,
+ canvasArea, aBackstop);
+}
+
+// #define PRINT_HITTESTINFO_STATS
+#ifdef PRINT_HITTESTINFO_STATS
+void PrintHitTestInfoStatsInternal(nsDisplayList* aList, int& aTotal,
+ int& aHitTest, int& aVisible,
+ int& aSpecial) {
+ for (nsDisplayItem* i : *aList) {
+ aTotal++;
+
+ if (i->GetChildren()) {
+ PrintHitTestInfoStatsInternal(i->GetChildren(), aTotal, aHitTest,
+ aVisible, aSpecial);
+ }
+
+ if (i->GetType() == DisplayItemType::TYPE_COMPOSITOR_HITTEST_INFO) {
+ aHitTest++;
+
+ const auto& hitTestInfo = static_cast<nsDisplayCompositorHitTestInfo*>(i)
+ ->GetHitTestInfo()
+ .Info();
+
+ if (hitTestInfo.size() > 1) {
+ aSpecial++;
+ continue;
+ }
+
+ if (hitTestInfo == CompositorHitTestFlags::eVisibleToHitTest) {
+ aVisible++;
+ continue;
+ }
+
+ aSpecial++;
+ }
+ }
+}
+
+void PrintHitTestInfoStats(nsDisplayList* aList) {
+ int total = 0;
+ int hitTest = 0;
+ int visible = 0;
+ int special = 0;
+
+ PrintHitTestInfoStatsInternal(aList, total, hitTest, visible, special);
+
+ double ratio = (double)hitTest / (double)total;
+
+ printf(
+ "List %p: total items: %d, hit test items: %d, ratio: %f, visible: %d, "
+ "special: %d\n",
+ aList, total, hitTest, ratio, visible, special);
+}
+#endif
+
+// Apply a batch of effects updates generated during a paint to their
+// respective remote browsers.
+static void ApplyEffectsUpdates(
+ const nsTHashMap<nsPtrHashKey<RemoteBrowser>, EffectsInfo>& aUpdates) {
+ for (const auto& entry : aUpdates) {
+ auto* browser = entry.GetKey();
+ const auto& update = entry.GetData();
+ browser->UpdateEffects(update);
+ }
+}
+
+static void DumpBeforePaintDisplayList(UniquePtr<std::stringstream>& aStream,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList,
+ const nsRect& aVisibleRect) {
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
+ nsCString string("dump-");
+ // Include the process ID in the dump file name, to make sure that in an
+ // e10s setup different processes don't clobber each other's dump files.
+ string.AppendInt(getpid());
+ for (int paintCount : *gPaintCountStack) {
+ string.AppendLiteral("-");
+ string.AppendInt(paintCount);
+ }
+ string.AppendLiteral(".html");
+ gfxUtils::sDumpPaintFile = fopen(string.BeginReading(), "w");
+ } else {
+ gfxUtils::sDumpPaintFile = stderr;
+ }
+ if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
+ *aStream << "<html><head><script>\n"
+ "var array = {};\n"
+ "function ViewImage(index) { \n"
+ " var image = document.getElementById(index);\n"
+ " if (image.src) {\n"
+ " image.removeAttribute('src');\n"
+ " } else {\n"
+ " image.src = array[index];\n"
+ " }\n"
+ "}</script></head><body>";
+ }
+#endif
+ *aStream << nsPrintfCString(
+ "Painting --- before optimization (dirty %d,%d,%d,%d):\n",
+ aVisibleRect.x, aVisibleRect.y, aVisibleRect.width,
+ aVisibleRect.height)
+ .get();
+ nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
+ gfxEnv::MOZ_DUMP_PAINT_TO_FILE());
+
+ if (gfxEnv::MOZ_DUMP_PAINT() || gfxEnv::MOZ_DUMP_PAINT_ITEMS()) {
+ // Flush stream now to avoid reordering dump output relative to
+ // messages dumped by PaintRoot below.
+ fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
+ aStream = MakeUnique<std::stringstream>();
+ }
+}
+
+static void DumpAfterPaintDisplayList(UniquePtr<std::stringstream>& aStream,
+ nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList) {
+ *aStream << "Painting --- after optimization:\n";
+ nsIFrame::PrintDisplayList(aBuilder, *aList, *aStream,
+ gfxEnv::MOZ_DUMP_PAINT_TO_FILE());
+
+ fprint_stderr(gfxUtils::sDumpPaintFile, *aStream);
+
+#ifdef MOZ_DUMP_PAINTING
+ if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
+ *aStream << "</body></html>";
+ }
+ if (gfxEnv::MOZ_DUMP_PAINT_TO_FILE()) {
+ fclose(gfxUtils::sDumpPaintFile);
+ }
+#endif
+
+ std::stringstream lsStream;
+ nsIFrame::PrintDisplayList(aBuilder, *aList, lsStream);
+}
+
+struct TemporaryDisplayListBuilder {
+ TemporaryDisplayListBuilder(nsIFrame* aFrame,
+ nsDisplayListBuilderMode aBuilderMode,
+ const bool aBuildCaret)
+ : mBuilder(aFrame, aBuilderMode, aBuildCaret), mList(&mBuilder) {}
+
+ ~TemporaryDisplayListBuilder() { mList.DeleteAll(&mBuilder); }
+
+ nsDisplayListBuilder mBuilder;
+ nsDisplayList mList;
+ RetainedDisplayListMetrics mMetrics;
+};
+
+void nsLayoutUtils::PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
+ const nsRegion& aDirtyRegion, nscolor aBackstop,
+ nsDisplayListBuilderMode aBuilderMode,
+ PaintFrameFlags aFlags) {
+ AUTO_PROFILER_LABEL("nsLayoutUtils::PaintFrame", GRAPHICS);
+
+ // Create a static storage counter that is incremented on eacy entry to
+ // PaintFrame and decremented on exit. We can use this later to determine if
+ // this is a top-level paint.
+ static uint32_t paintFrameDepth = 0;
+ ++paintFrameDepth;
+
+#ifdef MOZ_DUMP_PAINTING
+ if (!gPaintCountStack) {
+ gPaintCountStack = new nsTArray<int>();
+ ClearOnShutdown(&gPaintCountStack);
+
+ gPaintCountStack->AppendElement(0);
+ }
+ ++gPaintCountStack->LastElement();
+ AutoNestedPaintCount nestedPaintCount;
+#endif
+
+ nsIFrame* displayRoot = GetDisplayRootFrame(aFrame);
+
+ if (aFlags & PaintFrameFlags::WidgetLayers) {
+ nsView* view = aFrame->GetView();
+ if (!(view && view->GetWidget() && displayRoot == aFrame)) {
+ aFlags &= ~PaintFrameFlags::WidgetLayers;
+ NS_ASSERTION(aRenderingContext, "need a rendering context");
+ }
+ }
+
+ nsPresContext* presContext = aFrame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+
+ TimeStamp startBuildDisplayList = TimeStamp::Now();
+ auto dlTimerId = mozilla::glean::paint::build_displaylist_time.Start();
+
+ const bool buildCaret = !(aFlags & PaintFrameFlags::HideCaret);
+
+ // Note that isForPainting here does not include the PaintForPrinting builder
+ // mode; that's OK because there is no point in using retained display lists
+ // for a print destination.
+ const bool isForPainting = (aFlags & PaintFrameFlags::WidgetLayers) &&
+ aBuilderMode == nsDisplayListBuilderMode::Painting;
+
+ // Only allow retaining for painting when preffed on, and for root frames
+ // (since the modified frame tracking is per-root-frame).
+ const bool retainDisplayList =
+ isForPainting && AreRetainedDisplayListsEnabled() && !aFrame->GetParent();
+
+ RetainedDisplayListBuilder* retainedBuilder = nullptr;
+ Maybe<TemporaryDisplayListBuilder> temporaryBuilder;
+
+ nsDisplayListBuilder* builder = nullptr;
+ nsDisplayList* list = nullptr;
+ RetainedDisplayListMetrics* metrics = nullptr;
+
+ if (retainDisplayList) {
+ MOZ_ASSERT(aFrame == displayRoot);
+ retainedBuilder = aFrame->GetProperty(RetainedDisplayListBuilder::Cached());
+ if (!retainedBuilder) {
+ retainedBuilder =
+ new RetainedDisplayListBuilder(aFrame, aBuilderMode, buildCaret);
+ aFrame->SetProperty(RetainedDisplayListBuilder::Cached(),
+ retainedBuilder);
+ }
+
+ builder = retainedBuilder->Builder();
+ list = retainedBuilder->List();
+ metrics = retainedBuilder->Metrics();
+ } else {
+ temporaryBuilder.emplace(aFrame, aBuilderMode, buildCaret);
+ builder = &temporaryBuilder->mBuilder;
+ list = &temporaryBuilder->mList;
+ metrics = &temporaryBuilder->mMetrics;
+ }
+
+ MOZ_ASSERT(builder && list && metrics);
+
+ nsAutoString uri;
+ if (MOZ_LOG_TEST(GetLoggerByProcess(), LogLevel::Info) ||
+ MOZ_UNLIKELY(gfxUtils::DumpDisplayList()) ||
+ MOZ_UNLIKELY(gfxEnv::MOZ_DUMP_PAINT())) {
+ if (Document* doc = presContext->Document()) {
+ Unused << doc->GetDocumentURI(uri);
+ }
+ }
+
+ nsAutoString frameName, displayRootName;
+#ifdef DEBUG_FRAME_DUMP
+ if (MOZ_LOG_TEST(GetLoggerByProcess(), LogLevel::Info)) {
+ aFrame->GetFrameName(frameName);
+ displayRoot->GetFrameName(displayRootName);
+ }
+#endif
+
+ DL_LOGI("PaintFrame: %p (%s), DisplayRoot: %p (%s), Builder: %p, URI: %s",
+ aFrame, NS_ConvertUTF16toUTF8(frameName).get(), displayRoot,
+ NS_ConvertUTF16toUTF8(displayRootName).get(), retainedBuilder,
+ NS_ConvertUTF16toUTF8(uri).get());
+
+ metrics->Reset();
+ metrics->StartBuild();
+
+ builder->BeginFrame();
+
+ MOZ_ASSERT(paintFrameDepth >= 1);
+ // If this is a top-level paint, increment the paint sequence number.
+ if (paintFrameDepth == 1) {
+ // Increment the paint sequence number for the display list builder.
+ nsDisplayListBuilder::IncrementPaintSequenceNumber();
+ }
+
+ if (aFlags & PaintFrameFlags::InTransform) {
+ builder->SetInTransform(true);
+ }
+ if (aFlags & PaintFrameFlags::SyncDecodeImages) {
+ builder->SetSyncDecodeImages(true);
+ }
+ if (aFlags & (PaintFrameFlags::WidgetLayers | PaintFrameFlags::ToWindow)) {
+ builder->SetPaintingToWindow(true);
+ }
+ if (aFlags & PaintFrameFlags::UseHighQualityScaling) {
+ builder->SetUseHighQualityScaling(true);
+ }
+ if (aFlags & PaintFrameFlags::ForWebRender) {
+ builder->SetPaintingForWebRender(true);
+ }
+ if (aFlags & PaintFrameFlags::IgnoreSuppression) {
+ builder->IgnorePaintSuppression();
+ }
+
+ if (BrowsingContext* bc = presContext->Document()->GetBrowsingContext()) {
+ builder->SetInActiveDocShell(bc->IsActive());
+ }
+
+ nsRect rootInkOverflow = aFrame->InkOverflowRectRelativeToSelf();
+
+ // If the dynamic toolbar is completely collapsed, the visible rect should
+ // be expanded to include this area.
+ const bool hasDynamicToolbar =
+ presContext->IsRootContentDocumentCrossProcess() &&
+ presContext->HasDynamicToolbar();
+ if (hasDynamicToolbar) {
+ rootInkOverflow.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ presContext, rootInkOverflow.Size()));
+ }
+
+ // If we are in a remote browser, then apply clipping from ancestor browsers
+ if (BrowserChild* browserChild = BrowserChild::GetFrom(presShell)) {
+ if (!browserChild->IsTopLevel()) {
+ const nsRect unscaledVisibleRect =
+ browserChild->GetVisibleRect().valueOr(nsRect());
+ rootInkOverflow.IntersectRect(rootInkOverflow, unscaledVisibleRect);
+ }
+ }
+
+ builder->ClearHaveScrollableDisplayPort();
+ if (builder->IsPaintingToWindow() &&
+ nsLayoutUtils::AsyncPanZoomEnabled(aFrame)) {
+ DisplayPortUtils::MaybeCreateDisplayPortInFirstScrollFrameEncountered(
+ aFrame, builder);
+ }
+
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame && !aFrame->GetParent()) {
+ nsIScrollableFrame* rootScrollableFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ MOZ_ASSERT(rootScrollableFrame);
+ nsRect displayPortBase = rootInkOverflow;
+ nsRect temp = displayPortBase;
+ Unused << rootScrollableFrame->DecideScrollableLayer(
+ builder, &displayPortBase, &temp,
+ /* aSetBase = */ true);
+ }
+
+ nsRegion visibleRegion;
+ if (aFlags & PaintFrameFlags::WidgetLayers) {
+ // This layer tree will be reused, so we'll need to calculate it
+ // for the whole "visible" area of the window
+ //
+ // |ignoreViewportScrolling| and |usingDisplayPort| are persistent
+ // document-rendering state. We rely on PresShell to flush
+ // retained layers as needed when that persistent state changes.
+ visibleRegion = rootInkOverflow;
+ } else {
+ visibleRegion = aDirtyRegion;
+ }
+
+ Maybe<nsPoint> originalScrollPosition;
+ auto maybeResetScrollPosition = MakeScopeExit([&]() {
+ if (originalScrollPosition && rootScrollFrame) {
+ nsIScrollableFrame* rootScrollableFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ MOZ_ASSERT(rootScrollableFrame->GetScrolledFrame()->GetPosition() ==
+ nsPoint());
+ rootScrollableFrame->GetScrolledFrame()->SetPosition(
+ *originalScrollPosition);
+ }
+ });
+
+ nsRect canvasArea(nsPoint(0, 0),
+ aFrame->InkOverflowRectRelativeToSelf().Size());
+ bool ignoreViewportScrolling =
+ !aFrame->GetParent() && presShell->IgnoringViewportScrolling();
+
+ if (!aFrame->GetParent() && hasDynamicToolbar) {
+ canvasArea.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ presContext, canvasArea.Size()));
+ }
+
+ if (ignoreViewportScrolling && rootScrollFrame) {
+ nsIScrollableFrame* rootScrollableFrame =
+ presShell->GetRootScrollFrameAsScrollable();
+ if (aFlags & PaintFrameFlags::ResetViewportScrolling) {
+ // Temporarily scroll the root scroll frame to 0,0 so that position:fixed
+ // elements will appear fixed to the top-left of the document. We manually
+ // set the position of the scrolled frame instead of using ScrollTo, since
+ // the latter fires scroll listeners, which we don't want.
+ originalScrollPosition.emplace(
+ rootScrollableFrame->GetScrolledFrame()->GetPosition());
+ rootScrollableFrame->GetScrolledFrame()->SetPosition(nsPoint());
+ }
+ if (aFlags & PaintFrameFlags::DocumentRelative) {
+ // Make visibleRegion and aRenderingContext relative to the
+ // scrolled frame instead of the root frame.
+ nsPoint pos = rootScrollableFrame->GetScrollPosition();
+ visibleRegion.MoveBy(-pos);
+ if (aRenderingContext) {
+ gfxPoint devPixelOffset = nsLayoutUtils::PointToGfxPoint(
+ pos, presContext->AppUnitsPerDevPixel());
+ aRenderingContext->SetMatrixDouble(
+ aRenderingContext->CurrentMatrixDouble().PreTranslate(
+ devPixelOffset));
+ }
+ }
+ builder->SetIgnoreScrollFrame(rootScrollFrame);
+
+ nsCanvasFrame* canvasFrame =
+ do_QueryFrame(rootScrollableFrame->GetScrolledFrame());
+ if (canvasFrame) {
+ // Use UnionRect here to ensure that areas where the scrollbars
+ // were are still filled with the background color.
+ canvasArea.UnionRect(
+ canvasArea,
+ canvasFrame->CanvasArea() + builder->ToReferenceFrame(canvasFrame));
+ }
+ }
+
+ nsRect visibleRect = visibleRegion.GetBounds();
+ PartialUpdateResult updateState = PartialUpdateResult::Failed;
+
+ {
+ AUTO_PROFILER_LABEL_CATEGORY_PAIR(GRAPHICS_DisplayListBuilding);
+ AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayList", GRAPHICS);
+ PerfStats::AutoMetricRecording<PerfStats::Metric::DisplayListBuilding>
+ autoRecording;
+
+ ViewID id = ScrollableLayerGuid::NULL_SCROLL_ID;
+ nsDisplayListBuilder::AutoCurrentActiveScrolledRootSetter asrSetter(
+ builder);
+
+ if (presShell->GetDocument() &&
+ presShell->GetDocument()->IsRootDisplayDocument() &&
+ !presShell->GetRootScrollFrame()) {
+ // In cases where the root document is a XUL document, we want to take
+ // the ViewID from the root element, as that will be the ViewID of the
+ // root APZC in the tree. Skip doing this in cases where we know
+ // nsGfxScrollFrame::BuilDisplayList will do it instead.
+ if (dom::Element* element =
+ presShell->GetDocument()->GetDocumentElement()) {
+ id = nsLayoutUtils::FindOrCreateIDFor(element);
+ }
+ // In some cases we get a root document here on an APZ-enabled window
+ // that doesn't have the root displayport initialized yet, even though
+ // the ChromeProcessController is supposed to do it when the widget is
+ // created. This can happen simply because the ChromeProcessController
+ // does it on the next spin of the event loop, and we can trigger a
+ // paint synchronously after window creation but before that runs. In
+ // that case we should initialize the root displayport here before we do
+ // the paint.
+ } else if (XRE_IsParentProcess() && presContext->IsRoot() &&
+ presShell->GetDocument() != nullptr &&
+ presShell->GetRootScrollFrame() != nullptr &&
+ nsLayoutUtils::UsesAsyncScrolling(
+ presShell->GetRootScrollFrame())) {
+ if (dom::Element* element =
+ presShell->GetDocument()->GetDocumentElement()) {
+ if (!DisplayPortUtils::HasNonMinimalDisplayPort(element)) {
+ APZCCallbackHelper::InitializeRootDisplayport(presShell);
+ }
+ }
+ }
+
+ asrSetter.SetCurrentScrollParentId(id);
+
+ builder->SetVisibleRect(visibleRect);
+ builder->SetIsBuilding(true);
+ builder->SetAncestorHasApzAwareEventHandler(
+ gfxPlatform::AsyncPanZoomEnabled() &&
+ nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(presShell));
+
+ // If a pref is toggled that adds or removes display list items,
+ // we need to rebuild the display list. The pref may be toggled
+ // manually by the user, or during test setup.
+ if (retainDisplayList &&
+ !builder->ShouldRebuildDisplayListDueToPrefChange()) {
+ // Attempt to do a partial build and merge into the existing list.
+ // This calls BuildDisplayListForStacking context on a subset of the
+ // viewport.
+ updateState = retainedBuilder->AttemptPartialUpdate(aBackstop);
+ metrics->EndPartialBuild(updateState);
+ } else {
+ // Partial updates are disabled.
+ DL_LOGI("Partial updates are disabled");
+ metrics->mPartialUpdateResult = PartialUpdateResult::Failed;
+ metrics->mPartialUpdateFailReason = PartialUpdateFailReason::Disabled;
+ }
+
+ // Rebuild the full display list if the partial display list build failed.
+ bool doFullRebuild = updateState == PartialUpdateResult::Failed;
+
+ if (StaticPrefs::layout_display_list_build_twice()) {
+ // Build display list twice to compare partial and full display list
+ // build times.
+ metrics->StartBuild();
+ doFullRebuild = true;
+ }
+
+ if (doFullRebuild) {
+ if (retainDisplayList) {
+ retainedBuilder->ClearRetainedData();
+#ifdef DEBUG
+ mozilla::RDLUtils::AssertFrameSubtreeUnmodified(
+ builder->RootReferenceFrame());
+#endif
+ }
+
+ list->DeleteAll(builder);
+
+ builder->ClearRetainedWindowRegions();
+ builder->ClearWillChangeBudgets();
+
+ builder->EnterPresShell(aFrame);
+ builder->SetDirtyRect(visibleRect);
+
+ DL_LOGI("Starting full display list build, root frame: %p",
+ builder->RootReferenceFrame());
+
+ aFrame->BuildDisplayListForStackingContext(builder, list);
+ AddExtraBackgroundItems(builder, list, aFrame, canvasArea, visibleRegion,
+ aBackstop);
+
+ builder->LeavePresShell(aFrame, list);
+ metrics->EndFullBuild();
+
+ DL_LOGI("Finished full display list build");
+ updateState = PartialUpdateResult::Updated;
+ }
+
+ builder->SetIsBuilding(false);
+ builder->IncrementPresShellPaintCount(presShell);
+ }
+
+ MOZ_ASSERT(updateState != PartialUpdateResult::Failed);
+ builder->Check();
+
+ const double geckoDLBuildTime =
+ (TimeStamp::Now() - startBuildDisplayList).ToMilliseconds();
+ mozilla::glean::paint::build_displaylist_time.StopAndAccumulate(
+ std::move(dlTimerId));
+
+ bool consoleNeedsDisplayList =
+ (gfxUtils::DumpDisplayList() || gfxEnv::MOZ_DUMP_PAINT()) &&
+ builder->IsInActiveDocShell();
+#ifdef MOZ_DUMP_PAINTING
+ FILE* savedDumpFile = gfxUtils::sDumpPaintFile;
+#endif
+
+ UniquePtr<std::stringstream> ss;
+ if (consoleNeedsDisplayList) {
+ ss = MakeUnique<std::stringstream>();
+ *ss << "Display list for " << uri << "\n";
+ DumpBeforePaintDisplayList(ss, builder, list, visibleRect);
+ }
+
+ uint32_t flags = nsDisplayList::PAINT_DEFAULT;
+ if (aFlags & PaintFrameFlags::WidgetLayers) {
+ flags |= nsDisplayList::PAINT_USE_WIDGET_LAYERS;
+ if (!(aFlags & PaintFrameFlags::DocumentRelative)) {
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ if (widget) {
+ // If we're finished building display list items for painting of the
+ // outermost pres shell, notify the widget about any toolbars we've
+ // encountered.
+ widget->UpdateThemeGeometries(builder->GetThemeGeometries());
+ }
+ }
+ }
+ if (aFlags & PaintFrameFlags::ExistingTransaction) {
+ flags |= nsDisplayList::PAINT_EXISTING_TRANSACTION;
+ }
+ if (updateState == PartialUpdateResult::NoChange && !aRenderingContext) {
+ flags |= nsDisplayList::PAINT_IDENTICAL_DISPLAY_LIST;
+ }
+
+#ifdef PRINT_HITTESTINFO_STATS
+ if (XRE_IsContentProcess()) {
+ PrintHitTestInfoStats(list);
+ }
+#endif
+
+ TimeStamp paintStart = TimeStamp::Now();
+ list->PaintRoot(builder, aRenderingContext, flags, Some(geckoDLBuildTime));
+ Telemetry::AccumulateTimeDelta(Telemetry::PAINT_RASTERIZE_TIME, paintStart);
+
+ if (builder->IsPaintingToWindow()) {
+ presShell->EndPaint();
+ }
+ builder->Check();
+
+ if (consoleNeedsDisplayList) {
+ DumpAfterPaintDisplayList(ss, builder, list);
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ gfxUtils::sDumpPaintFile = savedDumpFile;
+#endif
+
+ // Update the widget's opaque region information. This sets
+ // glass boundaries on Windows. Also set up the window dragging region.
+ if ((aFlags & PaintFrameFlags::WidgetLayers) &&
+ !(aFlags & PaintFrameFlags::DocumentRelative)) {
+ if (nsIWidget* widget = aFrame->GetNearestWidget()) {
+ const nsRegion& opaqueRegion = builder->GetWindowOpaqueRegion();
+ widget->UpdateOpaqueRegion(LayoutDeviceIntRegion::FromUnknownRegion(
+ opaqueRegion.ToNearestPixels(presContext->AppUnitsPerDevPixel())));
+ widget->UpdateWindowDraggingRegion(builder->GetWindowDraggingRegion());
+ }
+ }
+
+ // Apply effects updates if we were actually painting
+ if (isForPainting) {
+ ApplyEffectsUpdates(builder->GetEffectUpdates());
+ }
+
+ builder->Check();
+
+ {
+ AUTO_PROFILER_TRACING_MARKER("Paint", "DisplayListResources", GRAPHICS);
+
+ builder->EndFrame();
+
+ if (temporaryBuilder) {
+ temporaryBuilder.reset();
+ }
+ }
+
+ --paintFrameDepth;
+#if 0
+ if (XRE_IsParentProcess()) {
+ if (metrics->mPartialUpdateResult == PartialUpdateResult::Failed) {
+ printf("DL partial update failed: %s, Frame: %p\n",
+ metrics->FailReasonString(), aFrame);
+ } else {
+ printf(
+ "DL partial build success!"
+ " new: %d, reused: %d, rebuilt: %d, removed: %d, total: %d\n",
+ metrics->mNewItems, metrics->mReusedItems, metrics->mRebuiltItems,
+ metrics->mRemovedItems, metrics->mTotalItems);
+ }
+ }
+#endif
+}
+
+/**
+ * Uses a binary search for find where the cursor falls in the line of text
+ * It also keeps track of the part of the string that has already been measured
+ * so it doesn't have to keep measuring the same text over and over
+ *
+ * @param "aBaseWidth" contains the width in twips of the portion
+ * of the text that has already been measured, and aBaseInx contains
+ * the index of the text that has already been measured.
+ *
+ * @param aTextWidth returns the (in twips) the length of the text that falls
+ * before the cursor aIndex contains the index of the text where the cursor
+ * falls
+ */
+bool nsLayoutUtils::BinarySearchForPosition(
+ DrawTarget* aDrawTarget, nsFontMetrics& aFontMetrics, const char16_t* aText,
+ int32_t aBaseWidth, int32_t aBaseInx, int32_t aStartInx, int32_t aEndInx,
+ int32_t aCursorPos, int32_t& aIndex, int32_t& aTextWidth) {
+ int32_t range = aEndInx - aStartInx;
+ if ((range == 1) || (range == 2 && NS_IS_HIGH_SURROGATE(aText[aStartInx]))) {
+ aIndex = aStartInx + aBaseInx;
+ aTextWidth = nsLayoutUtils::AppUnitWidthOfString(aText, aIndex,
+ aFontMetrics, aDrawTarget);
+ return true;
+ }
+
+ int32_t inx = aStartInx + (range / 2);
+
+ // Make sure we don't leave a dangling low surrogate
+ if (NS_IS_HIGH_SURROGATE(aText[inx - 1])) inx++;
+
+ int32_t textWidth = nsLayoutUtils::AppUnitWidthOfString(
+ aText, inx, aFontMetrics, aDrawTarget);
+
+ int32_t fullWidth = aBaseWidth + textWidth;
+ if (fullWidth == aCursorPos) {
+ aTextWidth = textWidth;
+ aIndex = inx;
+ return true;
+ } else if (aCursorPos < fullWidth) {
+ aTextWidth = aBaseWidth;
+ if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
+ aBaseInx, aStartInx, inx, aCursorPos, aIndex,
+ aTextWidth)) {
+ return true;
+ }
+ } else {
+ aTextWidth = fullWidth;
+ if (BinarySearchForPosition(aDrawTarget, aFontMetrics, aText, aBaseWidth,
+ aBaseInx, inx, aEndInx, aCursorPos, aIndex,
+ aTextWidth)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsLayoutUtils::AddBoxesForFrame(nsIFrame* aFrame,
+ nsLayoutUtils::BoxCallback* aCallback) {
+ auto pseudoType = aFrame->Style()->GetPseudoType();
+
+ if (pseudoType == PseudoStyleType::tableWrapper) {
+ AddBoxesForFrame(aFrame->PrincipalChildList().FirstChild(), aCallback);
+ if (aCallback->mIncludeCaptionBoxForTable) {
+ nsIFrame* kid =
+ aFrame->GetChildList(FrameChildListID::Caption).FirstChild();
+ if (kid) {
+ AddBoxesForFrame(kid, aCallback);
+ }
+ }
+ } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
+ pseudoType == PseudoStyleType::mozMathMLAnonymousBlock) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ AddBoxesForFrame(kid, aCallback);
+ }
+ } else {
+ aCallback->AddBox(aFrame);
+ }
+}
+
+void nsLayoutUtils::GetAllInFlowBoxes(nsIFrame* aFrame,
+ BoxCallback* aCallback) {
+ aCallback->mInTargetContinuation = false;
+ while (aFrame) {
+ AddBoxesForFrame(aFrame, aCallback);
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ aCallback->mInTargetContinuation = true;
+ }
+}
+
+nsIFrame* nsLayoutUtils::GetFirstNonAnonymousFrame(nsIFrame* aFrame) {
+ while (aFrame) {
+ auto pseudoType = aFrame->Style()->GetPseudoType();
+
+ if (pseudoType == PseudoStyleType::tableWrapper) {
+ nsIFrame* f =
+ GetFirstNonAnonymousFrame(aFrame->PrincipalChildList().FirstChild());
+ if (f) {
+ return f;
+ }
+ nsIFrame* kid =
+ aFrame->GetChildList(FrameChildListID::Caption).FirstChild();
+ if (kid) {
+ f = GetFirstNonAnonymousFrame(kid);
+ if (f) {
+ return f;
+ }
+ }
+ } else if (pseudoType == PseudoStyleType::mozBlockInsideInlineWrapper ||
+ pseudoType == PseudoStyleType::mozMathMLAnonymousBlock) {
+ for (nsIFrame* kid : aFrame->PrincipalChildList()) {
+ nsIFrame* f = GetFirstNonAnonymousFrame(kid);
+ if (f) {
+ return f;
+ }
+ }
+ } else {
+ return aFrame;
+ }
+
+ aFrame = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame);
+ }
+ return nullptr;
+}
+
+struct BoxToRect : public nsLayoutUtils::BoxCallback {
+ const nsIFrame* mRelativeTo;
+ RectCallback* mCallback;
+ uint32_t mFlags;
+ // If the frame we're measuring relative to is the root, we know all frames
+ // are descendants of it, so we don't need to compute the common ancestor
+ // between a frame and mRelativeTo.
+ bool mRelativeToIsRoot;
+ // For the same reason, if the frame we're measuring relative to is the target
+ // (this is useful for IntersectionObserver), we know all frames are
+ // descendants of it except if we're in a continuation or ib-split-sibling of
+ // it.
+ bool mRelativeToIsTarget;
+
+ BoxToRect(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
+ RectCallback* aCallback, uint32_t aFlags)
+ : mRelativeTo(aRelativeTo),
+ mCallback(aCallback),
+ mFlags(aFlags),
+ mRelativeToIsRoot(!aRelativeTo->GetParent()),
+ mRelativeToIsTarget(aRelativeTo == aTargetFrame) {}
+
+ void AddBox(nsIFrame* aFrame) override {
+ nsRect r;
+ nsIFrame* outer = SVGUtils::GetOuterSVGFrameAndCoveredRegion(aFrame, &r);
+ const bool usingSVGOuterFrame = !!outer;
+ if (!outer) {
+ outer = aFrame;
+ switch (mFlags & nsLayoutUtils::RECTS_WHICH_BOX_MASK) {
+ case nsLayoutUtils::RECTS_USE_CONTENT_BOX:
+ r = aFrame->GetContentRectRelativeToSelf();
+ break;
+ case nsLayoutUtils::RECTS_USE_PADDING_BOX:
+ r = aFrame->GetPaddingRectRelativeToSelf();
+ break;
+ case nsLayoutUtils::RECTS_USE_MARGIN_BOX:
+ r = aFrame->GetMarginRectRelativeToSelf();
+ break;
+ default: // Use the border box
+ r = aFrame->GetRectRelativeToSelf();
+ }
+ }
+ if (mFlags & nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS) {
+ const bool isAncestorKnown = [&] {
+ if (mRelativeToIsRoot) {
+ return true;
+ }
+ if (mRelativeToIsTarget && !mInTargetContinuation) {
+ return !usingSVGOuterFrame;
+ }
+ return false;
+ }();
+ if (isAncestorKnown) {
+ r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
+ } else {
+ nsLayoutUtils::TransformRect(outer, mRelativeTo, r);
+ }
+ } else {
+ if (aFrame->PresContext() != mRelativeTo->PresContext()) {
+ r += outer->GetOffsetToCrossDoc(mRelativeTo);
+ } else {
+ r += outer->GetOffsetTo(mRelativeTo);
+ }
+ }
+ mCallback->AddRect(r);
+ }
+};
+
+struct MOZ_RAII BoxToRectAndText : public BoxToRect {
+ Sequence<nsString>* mTextList;
+
+ BoxToRectAndText(const nsIFrame* aTargetFrame, const nsIFrame* aRelativeTo,
+ RectCallback* aCallback, Sequence<nsString>* aTextList,
+ uint32_t aFlags)
+ : BoxToRect(aTargetFrame, aRelativeTo, aCallback, aFlags),
+ mTextList(aTextList) {}
+
+ static void AccumulateText(nsIFrame* aFrame, nsAString& aResult) {
+ MOZ_ASSERT(aFrame);
+
+ // Get all the text in aFrame and child frames, while respecting
+ // the content offsets in each of the nsTextFrames.
+ if (aFrame->IsTextFrame()) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
+
+ nsIFrame::RenderedText renderedText = textFrame->GetRenderedText(
+ textFrame->GetContentOffset(),
+ textFrame->GetContentOffset() + textFrame->GetContentLength(),
+ nsIFrame::TextOffsetType::OffsetsInContentText,
+ nsIFrame::TrailingWhitespace::DontTrim);
+
+ aResult.Append(renderedText.mString);
+ }
+
+ for (nsIFrame* child = aFrame->PrincipalChildList().FirstChild(); child;
+ child = child->GetNextSibling()) {
+ AccumulateText(child, aResult);
+ }
+ }
+
+ void AddBox(nsIFrame* aFrame) override {
+ BoxToRect::AddBox(aFrame);
+ if (mTextList) {
+ nsString* textForFrame = mTextList->AppendElement(fallible);
+ if (textForFrame) {
+ AccumulateText(aFrame, *textForFrame);
+ }
+ }
+ }
+};
+
+void nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame,
+ const nsIFrame* aRelativeTo,
+ RectCallback* aCallback,
+ uint32_t aFlags) {
+ BoxToRect converter(aFrame, aRelativeTo, aCallback, aFlags);
+ GetAllInFlowBoxes(aFrame, &converter);
+}
+
+void nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame,
+ const nsIFrame* aRelativeTo,
+ RectCallback* aCallback,
+ Sequence<nsString>* aTextList,
+ uint32_t aFlags) {
+ BoxToRectAndText converter(aFrame, aRelativeTo, aCallback, aTextList, aFlags);
+ GetAllInFlowBoxes(aFrame, &converter);
+}
+
+nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
+
+void nsLayoutUtils::RectAccumulator::AddRect(const nsRect& aRect) {
+ mResultRect.UnionRect(mResultRect, aRect);
+ if (!mSeenFirstRect) {
+ mSeenFirstRect = true;
+ mFirstRect = aRect;
+ }
+}
+
+nsLayoutUtils::RectListBuilder::RectListBuilder(DOMRectList* aList)
+ : mRectList(aList) {}
+
+void nsLayoutUtils::RectListBuilder::AddRect(const nsRect& aRect) {
+ RefPtr<DOMRect> rect = new DOMRect(mRectList);
+
+ rect->SetLayoutRect(aRect);
+ mRectList->Append(rect);
+}
+
+nsIFrame* nsLayoutUtils::GetContainingBlockForClientRect(nsIFrame* aFrame) {
+ return aFrame->PresShell()->GetRootFrame();
+}
+
+nsRect nsLayoutUtils::GetAllInFlowRectsUnion(nsIFrame* aFrame,
+ const nsIFrame* aRelativeTo,
+ uint32_t aFlags) {
+ RectAccumulator accumulator;
+ GetAllInFlowRects(aFrame, aRelativeTo, &accumulator, aFlags);
+ return accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
+ : accumulator.mResultRect;
+}
+
+nsRect nsLayoutUtils::GetTextShadowRectsUnion(
+ const nsRect& aTextAndDecorationsRect, nsIFrame* aFrame, uint32_t aFlags) {
+ const nsStyleText* textStyle = aFrame->StyleText();
+ auto shadows = textStyle->mTextShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return aTextAndDecorationsRect;
+ }
+
+ nsRect resultRect = aTextAndDecorationsRect;
+ int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
+ for (auto& shadow : shadows) {
+ nsMargin blur =
+ nsContextBoxBlur::GetBlurRadiusMargin(shadow.blur.ToAppUnits(), A2D);
+ if ((aFlags & EXCLUDE_BLUR_SHADOWS) && blur != nsMargin(0, 0, 0, 0))
+ continue;
+
+ nsRect tmpRect(aTextAndDecorationsRect);
+
+ tmpRect.MoveBy(
+ nsPoint(shadow.horizontal.ToAppUnits(), shadow.vertical.ToAppUnits()));
+ tmpRect.Inflate(blur);
+
+ resultRect.UnionRect(resultRect, tmpRect);
+ }
+ return resultRect;
+}
+
+enum ObjectDimensionType { eWidth, eHeight };
+static nscoord ComputeMissingDimension(
+ const nsSize& aDefaultObjectSize, const AspectRatio& aIntrinsicRatio,
+ const Maybe<nscoord>& aSpecifiedWidth,
+ const Maybe<nscoord>& aSpecifiedHeight,
+ ObjectDimensionType aDimensionToCompute) {
+ // The "default sizing algorithm" computes the missing dimension as follows:
+ // (source: http://dev.w3.org/csswg/css-images-3/#default-sizing )
+
+ // 1. "If the object has an intrinsic aspect ratio, the missing dimension of
+ // the concrete object size is calculated using the intrinsic aspect
+ // ratio and the present dimension."
+ if (aIntrinsicRatio) {
+ // Fill in the missing dimension using the intrinsic aspect ratio.
+ if (aDimensionToCompute == eWidth) {
+ return aIntrinsicRatio.ApplyTo(*aSpecifiedHeight);
+ }
+ return aIntrinsicRatio.Inverted().ApplyTo(*aSpecifiedWidth);
+ }
+
+ // 2. "Otherwise, if the missing dimension is present in the object's
+ // intrinsic dimensions, [...]"
+ // NOTE: *Skipping* this case, because we already know it's not true -- we're
+ // in this function because the missing dimension is *not* present in
+ // the object's intrinsic dimensions.
+
+ // 3. "Otherwise, the missing dimension of the concrete object size is taken
+ // from the default object size. "
+ return (aDimensionToCompute == eWidth) ? aDefaultObjectSize.width
+ : aDefaultObjectSize.height;
+}
+
+/*
+ * This computes & returns the concrete object size of replaced content, if
+ * that content were to be rendered with "object-fit: none". (Or, if the
+ * element has neither an intrinsic height nor width, this method returns an
+ * empty Maybe<> object.)
+ *
+ * As specced...
+ * http://dev.w3.org/csswg/css-images-3/#valdef-object-fit-none
+ * ..we use "the default sizing algorithm with no specified size,
+ * and a default object size equal to the replaced element's used width and
+ * height."
+ *
+ * The default sizing algorithm is described here:
+ * http://dev.w3.org/csswg/css-images-3/#default-sizing
+ * Quotes in the function-impl are taken from that ^ spec-text.
+ *
+ * Per its final bulleted section: since there's no specified size,
+ * we run the default sizing algorithm using the object's intrinsic size in
+ * place of the specified size. But if the object has neither an intrinsic
+ * height nor an intrinsic width, then we instead return without populating our
+ * outparam, and we let the caller figure out the size (using a contain
+ * constraint).
+ */
+static Maybe<nsSize> MaybeComputeObjectFitNoneSize(
+ const nsSize& aDefaultObjectSize, const IntrinsicSize& aIntrinsicSize,
+ const AspectRatio& aIntrinsicRatio) {
+ // "If the object has an intrinsic height or width, its size is resolved as
+ // if its intrinsic dimensions were given as the specified size."
+ //
+ // So, first we check if we have an intrinsic height and/or width:
+ const Maybe<nscoord>& specifiedWidth = aIntrinsicSize.width;
+ const Maybe<nscoord>& specifiedHeight = aIntrinsicSize.height;
+
+ Maybe<nsSize> noneSize; // (the value we'll return)
+ if (specifiedWidth || specifiedHeight) {
+ // We have at least one specified dimension; use whichever dimension is
+ // specified, and compute the other one using our intrinsic ratio, or (if
+ // no valid ratio) using the default object size.
+ noneSize.emplace();
+
+ noneSize->width =
+ specifiedWidth
+ ? *specifiedWidth
+ : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
+ specifiedWidth, specifiedHeight, eWidth);
+
+ noneSize->height =
+ specifiedHeight
+ ? *specifiedHeight
+ : ComputeMissingDimension(aDefaultObjectSize, aIntrinsicRatio,
+ specifiedWidth, specifiedHeight, eHeight);
+ }
+ // [else:] "Otherwise [if there's neither an intrinsic height nor width], its
+ // size is resolved as a contain constraint against the default object size."
+ // We'll let our caller do that, to share code & avoid redundant
+ // computations; so, we return w/out populating noneSize.
+ return noneSize;
+}
+
+// Computes the concrete object size to render into, as described at
+// http://dev.w3.org/csswg/css-images-3/#concrete-size-resolution
+static nsSize ComputeConcreteObjectSize(const nsSize& aConstraintSize,
+ const IntrinsicSize& aIntrinsicSize,
+ const AspectRatio& aIntrinsicRatio,
+ StyleObjectFit aObjectFit) {
+ // Handle default behavior (filling the container) w/ fast early return.
+ // (Also: if there's no valid intrinsic ratio, then we have the "fill"
+ // behavior & just use the constraint size.)
+ if (MOZ_LIKELY(aObjectFit == StyleObjectFit::Fill) || !aIntrinsicRatio) {
+ return aConstraintSize;
+ }
+
+ // The type of constraint to compute (cover/contain), if needed:
+ Maybe<nsImageRenderer::FitType> fitType;
+
+ Maybe<nsSize> noneSize;
+ if (aObjectFit == StyleObjectFit::None ||
+ aObjectFit == StyleObjectFit::ScaleDown) {
+ noneSize = MaybeComputeObjectFitNoneSize(aConstraintSize, aIntrinsicSize,
+ aIntrinsicRatio);
+ if (!noneSize || aObjectFit == StyleObjectFit::ScaleDown) {
+ // Need to compute a 'CONTAIN' constraint (either for the 'none' size
+ // itself, or for comparison w/ the 'none' size to resolve 'scale-down'.)
+ fitType.emplace(nsImageRenderer::CONTAIN);
+ }
+ } else if (aObjectFit == StyleObjectFit::Cover) {
+ fitType.emplace(nsImageRenderer::COVER);
+ } else if (aObjectFit == StyleObjectFit::Contain) {
+ fitType.emplace(nsImageRenderer::CONTAIN);
+ }
+
+ Maybe<nsSize> constrainedSize;
+ if (fitType) {
+ constrainedSize.emplace(nsImageRenderer::ComputeConstrainedSize(
+ aConstraintSize, aIntrinsicRatio, *fitType));
+ }
+
+ // Now, we should have all the sizing information that we need.
+ switch (aObjectFit) {
+ // skipping StyleObjectFit::Fill; we handled it w/ early-return.
+ case StyleObjectFit::Contain:
+ case StyleObjectFit::Cover:
+ MOZ_ASSERT(constrainedSize);
+ return *constrainedSize;
+
+ case StyleObjectFit::None:
+ if (noneSize) {
+ return *noneSize;
+ }
+ MOZ_ASSERT(constrainedSize);
+ return *constrainedSize;
+
+ case StyleObjectFit::ScaleDown:
+ MOZ_ASSERT(constrainedSize);
+ if (noneSize) {
+ constrainedSize->width =
+ std::min(constrainedSize->width, noneSize->width);
+ constrainedSize->height =
+ std::min(constrainedSize->height, noneSize->height);
+ }
+ return *constrainedSize;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unexpected enum value for 'object-fit'");
+ return aConstraintSize; // fall back to (default) 'fill' behavior
+ }
+}
+
+// (Helper for HasInitialObjectFitAndPosition, to check
+// each "object-position" coord.)
+static bool IsCoord50Pct(const LengthPercentage& aCoord) {
+ return aCoord.ConvertsToPercentage() && aCoord.ToPercentage() == 0.5f;
+}
+
+// Indicates whether the given nsStylePosition has the initial values
+// for the "object-fit" and "object-position" properties.
+static bool HasInitialObjectFitAndPosition(const nsStylePosition* aStylePos) {
+ const Position& objectPos = aStylePos->mObjectPosition;
+
+ return aStylePos->mObjectFit == StyleObjectFit::Fill &&
+ IsCoord50Pct(objectPos.horizontal) && IsCoord50Pct(objectPos.vertical);
+}
+
+/* static */
+nsRect nsLayoutUtils::ComputeObjectDestRect(const nsRect& aConstraintRect,
+ const IntrinsicSize& aIntrinsicSize,
+ const AspectRatio& aIntrinsicRatio,
+ const nsStylePosition* aStylePos,
+ nsPoint* aAnchorPoint) {
+ // Step 1: Figure out our "concrete object size"
+ // (the size of the region we'll actually draw our image's pixels into).
+ nsSize concreteObjectSize =
+ ComputeConcreteObjectSize(aConstraintRect.Size(), aIntrinsicSize,
+ aIntrinsicRatio, aStylePos->mObjectFit);
+
+ // Step 2: Figure out how to align that region in the element's content-box.
+ nsPoint imageTopLeftPt, imageAnchorPt;
+ nsImageRenderer::ComputeObjectAnchorPoint(
+ aStylePos->mObjectPosition, aConstraintRect.Size(), concreteObjectSize,
+ &imageTopLeftPt, &imageAnchorPt);
+ // Right now, we're with respect to aConstraintRect's top-left point. We add
+ // that point here, to convert to the same broader coordinate space that
+ // aConstraintRect is in.
+ imageTopLeftPt += aConstraintRect.TopLeft();
+ imageAnchorPt += aConstraintRect.TopLeft();
+
+ if (aAnchorPoint) {
+ // Special-case: if our "object-fit" and "object-position" properties have
+ // their default values ("object-fit: fill; object-position:50% 50%"), then
+ // we'll override the calculated imageAnchorPt, and instead use the
+ // object's top-left corner.
+ //
+ // This special case is partly for backwards compatibility (since
+ // traditionally we've pixel-aligned the top-left corner of e.g. <img>
+ // elements), and partly because ComputeSnappedDrawingParameters produces
+ // less error if the anchor point is at the top-left corner. So, all other
+ // things being equal, we prefer that code path with less error.
+ if (HasInitialObjectFitAndPosition(aStylePos)) {
+ *aAnchorPoint = imageTopLeftPt;
+ } else {
+ *aAnchorPoint = imageAnchorPt;
+ }
+ }
+ return nsRect(imageTopLeftPt, concreteObjectSize);
+}
+
+already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForFrame(
+ const nsIFrame* aFrame, float aInflation) {
+ ComputedStyle* computedStyle = aFrame->Style();
+ uint8_t variantWidth = NS_FONT_VARIANT_WIDTH_NORMAL;
+ if (computedStyle->IsTextCombined()) {
+ MOZ_ASSERT(aFrame->IsTextFrame());
+ auto textFrame = static_cast<const nsTextFrame*>(aFrame);
+ auto clusters = textFrame->CountGraphemeClusters();
+ if (clusters == 2) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_HALF;
+ } else if (clusters == 3) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_THIRD;
+ } else if (clusters == 4) {
+ variantWidth = NS_FONT_VARIANT_WIDTH_QUARTER;
+ }
+ }
+ return GetFontMetricsForComputedStyle(computedStyle, aFrame->PresContext(),
+ aInflation, variantWidth);
+}
+
+already_AddRefed<nsFontMetrics> nsLayoutUtils::GetFontMetricsForComputedStyle(
+ const ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
+ float aInflation, uint8_t aVariantWidth) {
+ WritingMode wm(aComputedStyle);
+ const nsStyleFont* styleFont = aComputedStyle->StyleFont();
+ nsFontMetrics::Params params;
+ params.language = styleFont->mLanguage;
+ params.explicitLanguage = styleFont->mExplicitLanguage;
+ params.orientation = wm.IsVertical() && !wm.IsSideways()
+ ? nsFontMetrics::eVertical
+ : nsFontMetrics::eHorizontal;
+ // pass the user font set object into the device context to
+ // pass along to CreateFontGroup
+ params.userFontSet = aPresContext->GetUserFontSet();
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
+
+ // When aInflation is 1.0 and we don't require width variant, avoid
+ // making a local copy of the nsFont.
+ // This also avoids running font.size through floats when it is large,
+ // which would be lossy. Fortunately, in such cases, aInflation is
+ // guaranteed to be 1.0f.
+ if (aInflation == 1.0f && aVariantWidth == NS_FONT_VARIANT_WIDTH_NORMAL) {
+ return aPresContext->GetMetricsFor(styleFont->mFont, params);
+ }
+
+ nsFont font = styleFont->mFont;
+ MOZ_ASSERT(!std::isnan(float(font.size.ToCSSPixels())),
+ "Style font should never be NaN");
+ font.size.ScaleBy(aInflation);
+ if (MOZ_UNLIKELY(std::isnan(float(font.size.ToCSSPixels())))) {
+ font.size = {0};
+ }
+ font.variantWidth = aVariantWidth;
+ return aPresContext->GetMetricsFor(font, params);
+}
+
+nsIFrame* nsLayoutUtils::FindChildContainingDescendant(
+ nsIFrame* aParent, nsIFrame* aDescendantFrame) {
+ nsIFrame* result = aDescendantFrame;
+
+ while (result) {
+ nsIFrame* parent = result->GetParent();
+ if (parent == aParent) {
+ break;
+ }
+
+ // The frame is not an immediate child of aParent so walk up another level
+ result = parent;
+ }
+
+ return result;
+}
+
+nsBlockFrame* nsLayoutUtils::FindNearestBlockAncestor(nsIFrame* aFrame) {
+ nsIFrame* nextAncestor;
+ for (nextAncestor = aFrame->GetParent(); nextAncestor;
+ nextAncestor = nextAncestor->GetParent()) {
+ nsBlockFrame* block = do_QueryFrame(nextAncestor);
+ if (block) return block;
+ }
+ return nullptr;
+}
+
+nsIFrame* nsLayoutUtils::GetNonGeneratedAncestor(nsIFrame* aFrame) {
+ if (!aFrame->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT)) return aFrame;
+
+ nsIFrame* f = aFrame;
+ do {
+ f = GetParentOrPlaceholderFor(f);
+ } while (f->HasAnyStateBits(NS_FRAME_GENERATED_CONTENT));
+ return f;
+}
+
+nsIFrame* nsLayoutUtils::GetParentOrPlaceholderFor(const nsIFrame* aFrame) {
+ // This condition must match the condition in FindContainingBlocks in
+ // RetainedDisplayListBuider.cpp, MarkFrameForDisplayIfVisible and
+ // UnmarkFrameForDisplayIfVisible in nsDisplayList.cpp
+ if (aFrame->HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) &&
+ !aFrame->GetPrevInFlow()) {
+ return aFrame->GetProperty(nsIFrame::PlaceholderFrameProperty());
+ }
+ return aFrame->GetParent();
+}
+
+nsIFrame* nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(
+ const nsIFrame* aFrame) {
+ nsIFrame* f = GetParentOrPlaceholderFor(aFrame);
+ if (f) return f;
+ return GetCrossDocParentFrameInProcess(aFrame);
+}
+
+nsIFrame* nsLayoutUtils::GetDisplayListParent(nsIFrame* aFrame) {
+ if (aFrame->HasAnyStateBits(NS_FRAME_IS_PUSHED_FLOAT)) {
+ return aFrame->GetParent();
+ }
+ return nsLayoutUtils::GetParentOrPlaceholderForCrossDoc(aFrame);
+}
+
+nsIFrame* nsLayoutUtils::GetPrevContinuationOrIBSplitSibling(
+ const nsIFrame* aFrame) {
+ if (nsIFrame* result = aFrame->GetPrevContinuation()) {
+ return result;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // We are the first frame in the continuation chain. Get the ib-split prev
+ // sibling property stored in us.
+ return aFrame->GetProperty(nsIFrame::IBSplitPrevSibling());
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsLayoutUtils::GetNextContinuationOrIBSplitSibling(
+ const nsIFrame* aFrame) {
+ if (nsIFrame* result = aFrame->GetNextContinuation()) {
+ return result;
+ }
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ // We only store the ib-split sibling annotation with the first frame in the
+ // continuation chain.
+ return aFrame->FirstContinuation()->GetProperty(nsIFrame::IBSplitSibling());
+ }
+
+ return nullptr;
+}
+
+nsIFrame* nsLayoutUtils::FirstContinuationOrIBSplitSibling(
+ const nsIFrame* aFrame) {
+ nsIFrame* result = aFrame->FirstContinuation();
+
+ if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ while (auto* f = result->GetProperty(nsIFrame::IBSplitPrevSibling())) {
+ result = f;
+ }
+ }
+
+ return result;
+}
+
+nsIFrame* nsLayoutUtils::LastContinuationOrIBSplitSibling(
+ const nsIFrame* aFrame) {
+ nsIFrame* result = aFrame->FirstContinuation();
+
+ if (result->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT)) {
+ while (auto* f = result->GetProperty(nsIFrame::IBSplitSibling())) {
+ result = f;
+ }
+ }
+
+ return result->LastContinuation();
+}
+
+bool nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(
+ const nsIFrame* aFrame) {
+ if (aFrame->GetPrevContinuation()) {
+ return false;
+ }
+ if (aFrame->HasAnyStateBits(NS_FRAME_PART_OF_IBSPLIT) &&
+ aFrame->GetProperty(nsIFrame::IBSplitPrevSibling())) {
+ return false;
+ }
+
+ return true;
+}
+
+bool nsLayoutUtils::IsViewportScrollbarFrame(nsIFrame* aFrame) {
+ if (!aFrame) return false;
+
+ nsIFrame* rootScrollFrame = aFrame->PresShell()->GetRootScrollFrame();
+ if (!rootScrollFrame) return false;
+
+ nsIScrollableFrame* rootScrollableFrame = do_QueryFrame(rootScrollFrame);
+ NS_ASSERTION(rootScrollableFrame, "The root scorollable frame is null");
+
+ if (!IsProperAncestorFrame(rootScrollFrame, aFrame)) return false;
+
+ nsIFrame* rootScrolledFrame = rootScrollableFrame->GetScrolledFrame();
+ return !(rootScrolledFrame == aFrame ||
+ IsProperAncestorFrame(rootScrolledFrame, aFrame));
+}
+
+/**
+ * Use only for paddings / widths / heights, since it clamps negative calc() to
+ * 0.
+ */
+template <typename LengthPercentageLike>
+static bool GetAbsoluteCoord(const LengthPercentageLike& aStyle,
+ nscoord& aResult) {
+ if (!aStyle.ConvertsToLength()) {
+ return false;
+ }
+ aResult = std::max(0, aStyle.ToLength());
+ return true;
+}
+
+static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
+ nsIFrame* aFrame,
+ bool aHorizontalAxis,
+ bool aResolvesAgainstPaddingBox);
+
+static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
+ bool aHorizontalAxis, nscoord& aResult);
+
+// Only call on style coords for which GetAbsoluteCoord returned false.
+template <typename SizeOrMaxSize>
+static bool GetPercentBSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
+ bool aHorizontalAxis, nscoord& aResult) {
+ if (!aStyle.IsLengthPercentage()) {
+ return false;
+ }
+ return GetPercentBSize(aStyle.AsLengthPercentage(), aFrame, aHorizontalAxis,
+ aResult);
+}
+
+static bool GetPercentBSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
+ bool aHorizontalAxis, nscoord& aResult) {
+ if (!aStyle.HasPercent()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!aStyle.ConvertsToLength(),
+ "GetAbsoluteCoord should have handled this");
+
+ // During reflow, nsHTMLScrollFrame::ReflowScrolledFrame uses
+ // SetComputedHeight on the reflow input for its child to propagate its
+ // computed height to the scrolled content. So here we skip to the scroll
+ // frame that contains this scrolled content in order to get the same
+ // behavior as layout when computing percentage heights.
+ nsIFrame* f = aFrame->GetContainingBlock(nsIFrame::SKIP_SCROLLED_FRAME);
+ if (!f) {
+ MOZ_ASSERT_UNREACHABLE("top of frame tree not a containing block");
+ return false;
+ }
+
+ WritingMode wm = f->GetWritingMode();
+
+ const nsStylePosition* pos = f->StylePosition();
+ const auto& bSizeCoord = pos->BSize(wm);
+ nscoord h;
+ if (!GetAbsoluteCoord(bSizeCoord, h) &&
+ !GetPercentBSize(bSizeCoord, f, aHorizontalAxis, h)) {
+ LayoutFrameType fType = f->Type();
+ if (fType != LayoutFrameType::Viewport &&
+ fType != LayoutFrameType::Canvas &&
+ fType != LayoutFrameType::PageContent) {
+ // There's no basis for the percentage height, so it acts like auto.
+ // Should we consider a max-height < min-height pair a basis for
+ // percentage heights? The spec is somewhat unclear, and not doing
+ // so is simpler and avoids troubling discontinuities in behavior,
+ // so I'll choose not to. -LDB
+ return false;
+ }
+ // For the viewport, canvas, and page-content kids, the percentage
+ // basis is just the parent block-size.
+ h = f->BSize(wm);
+ if (h == NS_UNCONSTRAINEDSIZE) {
+ // We don't have a percentage basis after all
+ return false;
+ }
+ }
+
+ const auto& maxBSizeCoord = pos->MaxBSize(wm);
+
+ nscoord maxh;
+ if (GetAbsoluteCoord(maxBSizeCoord, maxh) ||
+ GetPercentBSize(maxBSizeCoord, f, aHorizontalAxis, maxh)) {
+ if (maxh < h) h = maxh;
+ }
+
+ const auto& minBSizeCoord = pos->MinBSize(wm);
+
+ nscoord minh;
+ if (GetAbsoluteCoord(minBSizeCoord, minh) ||
+ GetPercentBSize(minBSizeCoord, f, aHorizontalAxis, minh)) {
+ if (minh > h) {
+ h = minh;
+ }
+ }
+
+ // If we're an abspos box, percentages in that case resolve against the
+ // padding box.
+ //
+ // TODO: This could conceivably cause some problems with fieldsets (which are
+ // the other place that wants to ignore padding), but solving that here
+ // without hardcoding a check for f being a fieldset-content frame is a bit of
+ // a pain.
+ const bool resolvesAgainstPaddingBox = aFrame->IsAbsolutelyPositioned();
+ h += GetBSizePercentBasisAdjustment(pos->mBoxSizing, f, aHorizontalAxis,
+ resolvesAgainstPaddingBox);
+
+ aResult = std::max(aStyle.Resolve(std::max(h, 0)), 0);
+ return true;
+}
+
+// Return true if aStyle can be resolved to a definite value and if so
+// return that value in aResult.
+static bool GetDefiniteSize(const LengthPercentage& aStyle, nsIFrame* aFrame,
+ bool aIsInlineAxis,
+ const Maybe<LogicalSize>& aPercentageBasis,
+ nscoord* aResult) {
+ if (aStyle.ConvertsToLength()) {
+ *aResult = aStyle.ToLength();
+ return true;
+ }
+
+ if (!aPercentageBasis) {
+ return false;
+ }
+
+ auto wm = aFrame->GetWritingMode();
+ nscoord pb = aIsInlineAxis ? aPercentageBasis.value().ISize(wm)
+ : aPercentageBasis.value().BSize(wm);
+ if (pb == NS_UNCONSTRAINEDSIZE) {
+ return false;
+ }
+ *aResult = std::max(0, aStyle.Resolve(pb));
+ return true;
+}
+
+// Return true if aStyle can be resolved to a definite value and if so
+// return that value in aResult.
+template <typename SizeOrMaxSize>
+static bool GetDefiniteSize(const SizeOrMaxSize& aStyle, nsIFrame* aFrame,
+ bool aIsInlineAxis,
+ const Maybe<LogicalSize>& aPercentageBasis,
+ nscoord* aResult) {
+ if (!aStyle.IsLengthPercentage()) {
+ return false;
+ }
+ return GetDefiniteSize(aStyle.AsLengthPercentage(), aFrame, aIsInlineAxis,
+ aPercentageBasis, aResult);
+}
+
+// NOTE: this function will be replaced by GetDefiniteSizeTakenByBoxSizing (bug
+// 1363918). Please do not add new uses of this function.
+//
+// Get the amount of space to add or subtract out of aFrame's 'block-size' or
+// property value due its borders and paddings, given the box-sizing value in
+// aBoxSizing.
+//
+// aHorizontalAxis is true if our inline direction is horizontal and our block
+// direction is vertical. aResolvesAgainstPaddingBox is true if padding should
+// be added or not removed.
+static nscoord GetBSizePercentBasisAdjustment(StyleBoxSizing aBoxSizing,
+ nsIFrame* aFrame,
+ bool aHorizontalAxis,
+ bool aResolvesAgainstPaddingBox) {
+ nscoord adjustment = 0;
+ if (aBoxSizing == StyleBoxSizing::Border) {
+ const auto& border = aFrame->StyleBorder()->GetComputedBorder();
+ adjustment -= aHorizontalAxis ? border.TopBottom() : border.LeftRight();
+ }
+ if ((aBoxSizing == StyleBoxSizing::Border) == !aResolvesAgainstPaddingBox) {
+ const auto& stylePadding = aFrame->StylePadding()->mPadding;
+ const LengthPercentage& paddingStart =
+ stylePadding.Get(aHorizontalAxis ? eSideTop : eSideLeft);
+ const LengthPercentage& paddingEnd =
+ stylePadding.Get(aHorizontalAxis ? eSideBottom : eSideRight);
+ nscoord pad;
+ // XXXbz Calling GetPercentBSize on padding values looks bogus, since
+ // percent padding is always a percentage of the inline-size of the
+ // containing block. We should perhaps just treat non-absolute paddings
+ // here as 0 instead, except that in some cases the width may in fact be
+ // known. See bug 1231059.
+ if (GetAbsoluteCoord(paddingStart, pad) ||
+ GetPercentBSize(paddingStart, aFrame, aHorizontalAxis, pad)) {
+ adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
+ }
+ if (GetAbsoluteCoord(paddingEnd, pad) ||
+ GetPercentBSize(paddingEnd, aFrame, aHorizontalAxis, pad)) {
+ adjustment += aResolvesAgainstPaddingBox ? pad : -pad;
+ }
+ }
+ return adjustment;
+}
+
+// Get the amount of space taken out of aFrame's content area due to its
+// borders and paddings given the box-sizing value in aBoxSizing. We don't
+// get aBoxSizing from the frame because some callers want to compute this for
+// specific box-sizing values.
+// aIsInlineAxis is true if we're computing for aFrame's inline axis.
+// aIgnorePadding is true if padding should be ignored.
+static nscoord GetDefiniteSizeTakenByBoxSizing(
+ StyleBoxSizing aBoxSizing, nsIFrame* aFrame, bool aIsInlineAxis,
+ bool aIgnorePadding, const Maybe<LogicalSize>& aPercentageBasis) {
+ nscoord sizeTakenByBoxSizing = 0;
+ if (MOZ_UNLIKELY(aBoxSizing == StyleBoxSizing::Border)) {
+ const bool isHorizontalAxis =
+ aIsInlineAxis == !aFrame->GetWritingMode().IsVertical();
+ const nsStyleBorder* styleBorder = aFrame->StyleBorder();
+ sizeTakenByBoxSizing = isHorizontalAxis
+ ? styleBorder->GetComputedBorder().LeftRight()
+ : styleBorder->GetComputedBorder().TopBottom();
+ if (!aIgnorePadding) {
+ const auto& stylePadding = aFrame->StylePadding()->mPadding;
+ const LengthPercentage& pStart =
+ stylePadding.Get(isHorizontalAxis ? eSideLeft : eSideTop);
+ const LengthPercentage& pEnd =
+ stylePadding.Get(isHorizontalAxis ? eSideRight : eSideBottom);
+ nscoord pad;
+ // XXXbz Calling GetPercentBSize on padding values looks bogus, since
+ // percent padding is always a percentage of the inline-size of the
+ // containing block. We should perhaps just treat non-absolute paddings
+ // here as 0 instead, except that in some cases the width may in fact be
+ // known. See bug 1231059.
+ if (GetDefiniteSize(pStart, aFrame, aIsInlineAxis, aPercentageBasis,
+ &pad) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(pStart, aFrame, isHorizontalAxis, pad))) {
+ sizeTakenByBoxSizing += pad;
+ }
+ if (GetDefiniteSize(pEnd, aFrame, aIsInlineAxis, aPercentageBasis,
+ &pad) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(pEnd, aFrame, isHorizontalAxis, pad))) {
+ sizeTakenByBoxSizing += pad;
+ }
+ }
+ }
+ return sizeTakenByBoxSizing;
+}
+
+/**
+ * Handles only max-content and min-content, and
+ * -moz-fit-content for min-width and max-width, since the others
+ * (-moz-fit-content for width, and -moz-available) have no effect on
+ * intrinsic widths.
+ */
+static bool GetIntrinsicCoord(nsIFrame::ExtremumLength aStyle,
+ gfxContext* aRenderingContext, nsIFrame* aFrame,
+ Maybe<nscoord> aInlineSizeFromAspectRatio,
+ nsIFrame::SizeProperty aProperty,
+ nscoord aContentBoxToBoxSizingDiff,
+ nscoord& aResult) {
+ if (aStyle == nsIFrame::ExtremumLength::MozAvailable) {
+ return false;
+ }
+
+ if (aStyle == nsIFrame::ExtremumLength::FitContentFunction) {
+ // fit-content() should be handled by the caller.
+ return false;
+ }
+
+ if (aStyle == nsIFrame::ExtremumLength::FitContent) {
+ switch (aProperty) {
+ case nsIFrame::SizeProperty::Size:
+ // handle like 'width: auto'
+ return false;
+ case nsIFrame::SizeProperty::MaxSize:
+ // constrain large 'width' values down to max-content
+ aStyle = nsIFrame::ExtremumLength::MaxContent;
+ break;
+ case nsIFrame::SizeProperty::MinSize:
+ // constrain small 'width' or 'max-width' values up to min-content
+ aStyle = nsIFrame::ExtremumLength::MinContent;
+ break;
+ }
+ }
+
+ NS_ASSERTION(aStyle == nsIFrame::ExtremumLength::MinContent ||
+ aStyle == nsIFrame::ExtremumLength::MaxContent,
+ "should have reduced everything remaining to one of these");
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ if (aInlineSizeFromAspectRatio) {
+ aResult = *aInlineSizeFromAspectRatio;
+ } else if (aStyle == nsIFrame::ExtremumLength::MaxContent) {
+ aResult = aFrame->GetPrefISize(aRenderingContext);
+ } else {
+ aResult = aFrame->GetMinISize(aRenderingContext);
+ }
+
+ aResult += aContentBoxToBoxSizingDiff;
+
+ return true;
+}
+
+template <typename SizeOrMaxSize>
+static bool GetIntrinsicCoord(const SizeOrMaxSize& aStyle,
+ gfxContext* aRenderingContext, nsIFrame* aFrame,
+ Maybe<nscoord> aInlineSizeFromAspectRatio,
+ nsIFrame::SizeProperty aProperty,
+ nscoord aContentBoxToBoxSizingDiff,
+ nscoord& aResult) {
+ auto length = nsIFrame::ToExtremumLength(aStyle);
+ if (!length) {
+ return false;
+ }
+ return GetIntrinsicCoord(*length, aRenderingContext, aFrame,
+ aInlineSizeFromAspectRatio, aProperty,
+ aContentBoxToBoxSizingDiff, aResult);
+}
+
+#undef DEBUG_INTRINSIC_WIDTH
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+static int32_t gNoiseIndent = 0;
+#endif
+
+static nscoord GetFitContentSizeForMaxOrPreferredSize(
+ const IntrinsicISizeType aType, const nsIFrame::SizeProperty aProperty,
+ const nsIFrame* aFrame, const LengthPercentage& aStyleSize,
+ const nscoord aInitialValue, const nscoord aMinContentSize,
+ const nscoord aMaxContentSize) {
+ MOZ_ASSERT(aProperty != nsIFrame::SizeProperty::MinSize);
+
+ nscoord size = NS_UNCONSTRAINEDSIZE;
+ // 1. Treat fit-content()'s arg as a plain LengthPercentage
+ // However, we have to handle the cyclic percentage contribution first.
+ //
+ // https://drafts.csswg.org/css-sizing-3/#cyclic-percentage-contribution
+ if (aType == IntrinsicISizeType::MinISize &&
+ aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aProperty)) {
+ // Case (c) in the spec.
+ // FIXME: This doesn't follow the spec for calc(). We should fix this in
+ // Bug 1463700.
+ size = 0;
+ } else if (!GetAbsoluteCoord(aStyleSize, size)) {
+ // As initial value. Case (a) and (b) in the spec.
+ size = aInitialValue;
+ }
+
+ // 2. Clamp size by min-content and max-content.
+ return std::max(aMinContentSize, std::min(aMaxContentSize, size));
+}
+
+/**
+ * Add aOffsets which describes what to add on outside of the content box
+ * aContentSize (controlled by 'box-sizing') and apply min/max properties.
+ * We have to account for these properties after getting all the offsets
+ * (margin, border, padding) because percentages do not operate linearly.
+ * Doing this is ok because although percentages aren't handled linearly,
+ * they are handled monotonically.
+ *
+ * @param aContentSize the content size calculated so far
+ (@see IntrinsicForContainer)
+ * @param aContentMinSize ditto min content size
+ * @param aStyleSize a 'width' or 'height' property value
+ * @param aFixedMinSize if aStyleMinSize is a definite size then this points to
+ * the value, otherwise nullptr
+ * @param aStyleMinSize a 'min-width' or 'min-height' property value
+ * @param aFixedMaxSize if aStyleMaxSize is a definite size then this points to
+ * the value, otherwise nullptr
+ * @param aStyleMaxSize a 'max-width' or 'max-height' property value
+ * @param aInlineSizeFromAspectRatio the content-box inline size computed from
+ * aspect-ratio and the definite block size.
+ * We use this value to resolve
+ * {min|max}-content.
+ * @param aFlags same as for IntrinsicForContainer
+ * @param aContainerWM the container's WM
+ */
+static nscoord AddIntrinsicSizeOffset(
+ gfxContext* aRenderingContext, nsIFrame* aFrame,
+ const nsIFrame::IntrinsicSizeOffsetData& aOffsets, IntrinsicISizeType aType,
+ StyleBoxSizing aBoxSizing, nscoord aContentSize, nscoord aContentMinSize,
+ const StyleSize& aStyleSize, const nscoord* aFixedMinSize,
+ const StyleSize& aStyleMinSize, const nscoord* aFixedMaxSize,
+ const StyleMaxSize& aStyleMaxSize,
+ Maybe<nscoord> aInlineSizeFromAspectRatio, uint32_t aFlags,
+ PhysicalAxis aAxis) {
+ nscoord result = aContentSize;
+ nscoord min = aContentMinSize;
+ nscoord coordOutsideSize = 0;
+ nscoord contentBoxToBoxSizingDiff = 0;
+
+ if (!(aFlags & nsLayoutUtils::IGNORE_PADDING)) {
+ coordOutsideSize += aOffsets.padding;
+ }
+
+ coordOutsideSize += aOffsets.border;
+
+ if (aBoxSizing == StyleBoxSizing::Border) {
+ // Store the padding of the box so we can pass it into
+ // functions that works with content box-sizing.
+ contentBoxToBoxSizingDiff = coordOutsideSize;
+
+ min += coordOutsideSize;
+ result = NSCoordSaturatingAdd(result, coordOutsideSize);
+
+ coordOutsideSize = 0;
+ }
+
+ coordOutsideSize += aOffsets.margin;
+
+ min += coordOutsideSize;
+
+ // Compute min-content/max-content for fit-content().
+ nscoord minContent = 0;
+ nscoord maxContent = NS_UNCONSTRAINEDSIZE;
+ if (aStyleSize.IsFitContentFunction() ||
+ aStyleMaxSize.IsFitContentFunction() ||
+ aStyleMinSize.IsFitContentFunction()) {
+ if (aInlineSizeFromAspectRatio) {
+ minContent = maxContent = *aInlineSizeFromAspectRatio;
+ } else {
+ minContent = aFrame->GetMinISize(aRenderingContext);
+ maxContent = aFrame->GetPrefISize(aRenderingContext);
+ }
+ minContent += contentBoxToBoxSizingDiff;
+ maxContent += contentBoxToBoxSizingDiff;
+ }
+
+ // Compute size.
+ nscoord size = NS_UNCONSTRAINEDSIZE;
+ if (aType == IntrinsicISizeType::MinISize &&
+ aFrame->IsPercentageResolvedAgainstZero(aStyleSize, aStyleMaxSize)) {
+ // XXX bug 1463700: this doesn't handle calc() according to spec
+ result = 0; // let |min| handle padding/border/margin
+ } else if (GetAbsoluteCoord(aStyleSize, size) ||
+ GetIntrinsicCoord(aStyleSize, aRenderingContext, aFrame,
+ aInlineSizeFromAspectRatio,
+ nsIFrame::SizeProperty::Size,
+ contentBoxToBoxSizingDiff, size)) {
+ result = size + coordOutsideSize;
+ } else if (aStyleSize.IsFitContentFunction()) {
+ // |result| here is the content size or border size, depends on
+ // StyleBoxSizing. We use it as the initial value when handling the cyclic
+ // percentage.
+ nscoord initial = result;
+ nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
+ aType, nsIFrame::SizeProperty::Size, aFrame,
+ aStyleSize.AsFitContentFunction(), initial, minContent, maxContent);
+ // Add border and padding.
+ result = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
+ } else {
+ result = NSCoordSaturatingAdd(result, coordOutsideSize);
+ }
+
+ // Compute max-size.
+ nscoord maxSize = aFixedMaxSize ? *aFixedMaxSize : 0;
+ if (aFixedMaxSize || GetIntrinsicCoord(aStyleMaxSize, aRenderingContext,
+ aFrame, aInlineSizeFromAspectRatio,
+ nsIFrame::SizeProperty::MaxSize,
+ contentBoxToBoxSizingDiff, maxSize)) {
+ maxSize += coordOutsideSize;
+ if (result > maxSize) {
+ result = maxSize;
+ }
+ } else if (aStyleMaxSize.IsFitContentFunction()) {
+ nscoord fitContentFuncSize = GetFitContentSizeForMaxOrPreferredSize(
+ aType, nsIFrame::SizeProperty::MaxSize, aFrame,
+ aStyleMaxSize.AsFitContentFunction(), NS_UNCONSTRAINEDSIZE, minContent,
+ maxContent);
+ maxSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
+ if (result > maxSize) {
+ result = maxSize;
+ }
+ }
+
+ // Compute min-size.
+ nscoord minSize = aFixedMinSize ? *aFixedMinSize : 0;
+ if (aFixedMinSize || GetIntrinsicCoord(aStyleMinSize, aRenderingContext,
+ aFrame, aInlineSizeFromAspectRatio,
+ nsIFrame::SizeProperty::MinSize,
+ contentBoxToBoxSizingDiff, minSize)) {
+ minSize += coordOutsideSize;
+ if (result < minSize) {
+ result = minSize;
+ }
+ } else if (aStyleMinSize.IsFitContentFunction()) {
+ if (!GetAbsoluteCoord(aStyleMinSize.AsFitContentFunction(), minSize)) {
+ // FIXME: Bug 1463700, we should resolve only the percentage part to 0
+ // such as min-width: fit-content(calc(50% + 50px)).
+ minSize = 0;
+ }
+ nscoord fitContentFuncSize =
+ std::max(minContent, std::min(maxContent, minSize));
+ minSize = NSCoordSaturatingAdd(fitContentFuncSize, coordOutsideSize);
+ if (result < minSize) {
+ result = minSize;
+ }
+ }
+
+ if (result < min) {
+ result = min;
+ }
+
+ const nsStyleDisplay* disp = aFrame->StyleDisplay();
+ if (aFrame->IsThemed(disp)) {
+ nsPresContext* pc = aFrame->PresContext();
+ LayoutDeviceIntSize devSize = pc->Theme()->GetMinimumWidgetSize(
+ pc, aFrame, disp->EffectiveAppearance());
+ nscoord themeSize = pc->DevPixelsToAppUnits(
+ aAxis == eAxisVertical ? devSize.height : devSize.width);
+ // GetMinimumWidgetSize() returns a border-box width.
+ themeSize += aOffsets.margin;
+ if (themeSize > result) {
+ result = themeSize;
+ }
+ }
+ return result;
+}
+
+static void AddStateBitToAncestors(nsIFrame* aFrame, nsFrameState aBit) {
+ for (nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ if (f->HasAnyStateBits(aBit)) {
+ break;
+ }
+ f->AddStateBits(aBit);
+ }
+}
+
+/* static */
+nscoord nsLayoutUtils::IntrinsicForAxis(
+ PhysicalAxis aAxis, gfxContext* aRenderingContext, nsIFrame* aFrame,
+ IntrinsicISizeType aType, const Maybe<LogicalSize>& aPercentageBasis,
+ uint32_t aFlags, nscoord aMarginBoxMinSizeClamp) {
+ MOZ_ASSERT(aFrame, "null frame");
+ MOZ_ASSERT(aFrame->GetParent(),
+ "IntrinsicForAxis called on frame not in tree");
+ MOZ_ASSERT(aFrame->GetParent()->Type() != LayoutFrameType::GridContainer ||
+ aPercentageBasis.isSome(),
+ "grid layout should always pass a percentage basis");
+
+ const bool horizontalAxis = MOZ_LIKELY(aAxis == eAxisHorizontal);
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size for container:\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical");
+#endif
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ // We want the size this frame will contribute to the parent's inline-size,
+ // so we work in the parent's writing mode; but if aFrame is orthogonal to
+ // its parent, we'll need to look at its BSize instead of min/pref-ISize.
+ const nsStylePosition* stylePos = aFrame->StylePosition();
+ StyleBoxSizing boxSizing = stylePos->mBoxSizing;
+
+ StyleSize styleMinISize =
+ horizontalAxis ? stylePos->mMinWidth : stylePos->mMinHeight;
+ StyleSize styleISize =
+ (aFlags & MIN_INTRINSIC_ISIZE)
+ ? styleMinISize
+ : (horizontalAxis ? stylePos->mWidth : stylePos->mHeight);
+ MOZ_ASSERT(!(aFlags & MIN_INTRINSIC_ISIZE) || styleISize.IsAuto() ||
+ nsIFrame::ToExtremumLength(styleISize),
+ "should only use MIN_INTRINSIC_ISIZE for intrinsic values");
+ StyleMaxSize styleMaxISize =
+ horizontalAxis ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+
+ PhysicalAxis ourInlineAxis =
+ aFrame->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ const bool isInlineAxis = aAxis == ourInlineAxis;
+
+ auto resetIfKeywords = [](StyleSize& aSize, StyleSize& aMinSize,
+ StyleMaxSize& aMaxSize) {
+ if (!aSize.IsLengthPercentage()) {
+ aSize = StyleSize::Auto();
+ }
+ if (!aMinSize.IsLengthPercentage()) {
+ aMinSize = StyleSize::Auto();
+ }
+ if (!aMaxSize.IsLengthPercentage()) {
+ aMaxSize = StyleMaxSize::None();
+ }
+ };
+ // According to the spec, max-content and min-content should behave as the
+ // property's initial values in block axis.
+ // It also make senses to use the initial values for -moz-fit-content and
+ // -moz-available for intrinsic size in block axis. Therefore, we reset them
+ // if needed.
+ if (!isInlineAxis) {
+ resetIfKeywords(styleISize, styleMinISize, styleMaxISize);
+ }
+
+ // We build up two values starting with the content box, and then
+ // adding padding, border and margin. The result is normally
+ // |result|. Then, when we handle 'width', 'min-width', and
+ // 'max-width', we use the results we've been building in |min| as a
+ // minimum, overriding 'min-width'. This ensures two things:
+ // * that we don't let a value of 'box-sizing' specifying a width
+ // smaller than the padding/border inside the box-sizing box give
+ // a content width less than zero
+ // * that we prevent tables from becoming smaller than their
+ // intrinsic minimum width
+ nscoord result = 0, min = 0;
+
+ nscoord maxISize;
+ bool haveFixedMaxISize = GetAbsoluteCoord(styleMaxISize, maxISize);
+ nscoord minISize;
+
+ // Treat "min-width: auto" as 0.
+ bool haveFixedMinISize;
+ if (styleMinISize.IsAuto()) {
+ // 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;
+ haveFixedMinISize = true;
+ } else {
+ haveFixedMinISize = GetAbsoluteCoord(styleMinISize, minISize);
+ }
+
+ auto childWM = aFrame->GetWritingMode();
+ nscoord pmPercentageBasis = NS_UNCONSTRAINEDSIZE;
+ if (aPercentageBasis.isSome()) {
+ // The padding/margin percentage basis is the inline-size in the parent's
+ // writing-mode.
+ pmPercentageBasis =
+ aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
+ ? aPercentageBasis->BSize(childWM)
+ : aPercentageBasis->ISize(childWM);
+ }
+ nsIFrame::IntrinsicSizeOffsetData offsets =
+ MOZ_LIKELY(isInlineAxis)
+ ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
+ : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
+
+ auto getContentBoxSizeToBoxSizingAdjust =
+ [childWM, &offsets, &aFrame, isInlineAxis,
+ pmPercentageBasis](const StyleBoxSizing aBoxSizing) {
+ return aBoxSizing == StyleBoxSizing::Border
+ ? LogicalSize(childWM,
+ (isInlineAxis ? offsets
+ : aFrame->IntrinsicISizeOffsets(
+ pmPercentageBasis))
+ .BorderPadding(),
+ (!isInlineAxis ? offsets
+ : aFrame->IntrinsicBSizeOffsets(
+ pmPercentageBasis))
+ .BorderPadding())
+ : LogicalSize(childWM);
+ };
+
+ Maybe<nscoord> inlineSizeFromAspectRatio;
+ Maybe<LogicalSize> contentBoxSizeToBoxSizingAdjust;
+
+ const bool ignorePadding =
+ (aFlags & IGNORE_PADDING) || aFrame->IsAbsolutelyPositioned();
+
+ // If we have a specified width (or a specified 'min-width' greater
+ // than the specified 'max-width', which works out to the same thing),
+ // don't even bother getting the frame's intrinsic width, because in
+ // this case GetAbsoluteCoord(styleISize, w) will always succeed, so
+ // we'll never need the intrinsic dimensions.
+ if (styleISize.IsMaxContent() || styleISize.IsMinContent()) {
+ MOZ_ASSERT(isInlineAxis);
+ // -moz-fit-content and -moz-available enumerated widths compute intrinsic
+ // widths just like auto.
+ // For max-content and min-content, we handle them like
+ // specified widths, but ignore box-sizing.
+ boxSizing = StyleBoxSizing::Content;
+ } else if (!styleISize.ConvertsToLength() &&
+ !(styleISize.IsFitContentFunction() &&
+ styleISize.AsFitContentFunction().ConvertsToLength()) &&
+ !(haveFixedMinISize && haveFixedMaxISize &&
+ maxISize <= minISize)) {
+#ifdef DEBUG_INTRINSIC_WIDTH
+ ++gNoiseIndent;
+#endif
+ if (MOZ_UNLIKELY(!isInlineAxis)) {
+ IntrinsicSize intrinsicSize = aFrame->GetIntrinsicSize();
+ const auto& intrinsicBSize =
+ horizontalAxis ? intrinsicSize.width : intrinsicSize.height;
+ if (intrinsicBSize) {
+ result = *intrinsicBSize;
+ } else {
+ // We don't have an intrinsic bsize and we need aFrame's block-dir size.
+ if (aFlags & BAIL_IF_REFLOW_NEEDED) {
+ return NS_INTRINSIC_ISIZE_UNKNOWN;
+ }
+ // XXX Unfortunately, we probably don't know this yet, so this is
+ // wrong... but it's not clear what we should do. If aFrame's inline
+ // size hasn't been determined yet, we can't necessarily figure out its
+ // block size either. For now, authors who put orthogonal elements into
+ // things like buttons or table cells may have to explicitly provide
+ // sizes rather than expecting intrinsic sizing to work "perfectly" in
+ // underspecified cases.
+ result = aFrame->BSize();
+ }
+ } else {
+ result = aType == IntrinsicISizeType::MinISize
+ ? aFrame->GetMinISize(aRenderingContext)
+ : aFrame->GetPrefISize(aRenderingContext);
+ }
+#ifdef DEBUG_INTRINSIC_WIDTH
+ --gNoiseIndent;
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size from frame is %d.\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical", result);
+#endif
+
+ // Handle elements with an intrinsic ratio (or size) and a specified
+ // height, min-height, or max-height.
+ // NOTE:
+ // 1. We treat "min-height:auto" as "0" for the purpose of this code,
+ // since that's what it means in all cases except for on flex items -- and
+ // even there, we're supposed to ignore it (i.e. treat it as 0) until the
+ // flex container explicitly considers it.
+ // 2. The 'B' in |styleBSize|, |styleMinBSize|, and |styleMaxBSize|
+ // represents the ratio-determining axis of |aFrame|. It could be the inline
+ // axis or the block axis of |aFrame|. (So we are calculating the size
+ // along the ratio-dependent axis in this if-branch.)
+ StyleSize styleBSize =
+ horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
+ StyleSize styleMinBSize =
+ horizontalAxis ? stylePos->mMinHeight : stylePos->mMinWidth;
+ StyleMaxSize styleMaxBSize =
+ horizontalAxis ? stylePos->mMaxHeight : stylePos->mMaxWidth;
+
+ // According to the spec, max-content and min-content should behave as the
+ // property's initial values in block axis.
+ // It also make senses to use the initial values for -moz-fit-content and
+ // -moz-available for intrinsic size in block axis. Therefore, we reset them
+ // if needed.
+ if (isInlineAxis) {
+ resetIfKeywords(styleBSize, styleMinBSize, styleMaxBSize);
+ }
+
+ // If our BSize or min/max-BSize properties are set to values that we can
+ // resolve and that will impose a constraint when transferred through our
+ // aspect ratio (if we have one), then compute and apply that constraint.
+ //
+ // (Note: This helper-bool & lambda just let us optimize away the actual
+ // transferring-and-clamping arithmetic, for the common case where we can
+ // tell that none of the block-axis size properties establish a meaningful
+ // transferred constraint.)
+ const bool mightHaveBlockAxisConstraintToTransfer = [&] {
+ if (!styleBSize.BehavesLikeInitialValueOnBlockAxis()) {
+ return true; // BSize property might have a constraint to transfer.
+ }
+ // Check for min-BSize values that would obviously produce zero in the
+ // transferring logic that follows; zero is trivially-ignorable as a
+ // transferred lower-bound. (These include the the property's initial
+ // value, explicit 0, and values that are equivalent to these.)
+ bool minBSizeHasNoConstraintToTransfer =
+ styleMinBSize.BehavesLikeInitialValueOnBlockAxis() ||
+ (styleMinBSize.IsLengthPercentage() &&
+ styleMinBSize.AsLengthPercentage().IsDefinitelyZero());
+ if (!minBSizeHasNoConstraintToTransfer) {
+ return true; // min-BSize property might have a constraint to transfer.
+ }
+ if (!styleMaxBSize.BehavesLikeInitialValueOnBlockAxis()) {
+ return true; // max-BSize property might have a constraint to transfer.
+ }
+ return false;
+ }();
+ if (mightHaveBlockAxisConstraintToTransfer) {
+ if (AspectRatio ratio = aFrame->GetAspectRatio()) {
+ AddStateBitToAncestors(
+ aFrame, NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE);
+
+ nscoord bSizeTakenByBoxSizing = GetDefiniteSizeTakenByBoxSizing(
+ boxSizing, aFrame, !isInlineAxis, ignorePadding, aPercentageBasis);
+ contentBoxSizeToBoxSizingAdjust.emplace(
+ getContentBoxSizeToBoxSizingAdjust(boxSizing));
+ // NOTE: This is only the minContentSize if we've been passed
+ // MIN_INTRINSIC_ISIZE (which is fine, because this should only be used
+ // inside a check for that flag).
+ nscoord minContentSize = result;
+ nscoord h;
+ if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
+ &h) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(styleBSize, aFrame, horizontalAxis, h))) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ // We are computing the size of |aFrame|, so we use the inline & block
+ // dimensions of |aFrame|.
+ result = ratio.ComputeRatioDependentSize(
+ isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
+ *contentBoxSizeToBoxSizingAdjust);
+ // We have get the inlineSizeForAspectRatio value, so we don't have to
+ // recompute this again below.
+ inlineSizeFromAspectRatio.emplace(result);
+ }
+
+ if (GetDefiniteSize(styleMaxBSize, aFrame, !isInlineAxis,
+ aPercentageBasis, &h) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(styleMaxBSize, aFrame, horizontalAxis, h))) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ nscoord maxISize = ratio.ComputeRatioDependentSize(
+ isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
+ *contentBoxSizeToBoxSizingAdjust);
+ if (maxISize < result) {
+ result = maxISize;
+ }
+ if (maxISize < minContentSize) {
+ minContentSize = maxISize;
+ }
+ }
+
+ if (GetDefiniteSize(styleMinBSize, aFrame, !isInlineAxis,
+ aPercentageBasis, &h) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(styleMinBSize, aFrame, horizontalAxis, h))) {
+ h = std::max(0, h - bSizeTakenByBoxSizing);
+ nscoord minISize = ratio.ComputeRatioDependentSize(
+ isInlineAxis ? eLogicalAxisInline : eLogicalAxisBlock, childWM, h,
+ *contentBoxSizeToBoxSizingAdjust);
+ if (minISize > result) {
+ result = minISize;
+ }
+ if (minISize > minContentSize) {
+ minContentSize = minISize;
+ }
+ }
+
+ if (MOZ_UNLIKELY(aFlags & nsLayoutUtils::MIN_INTRINSIC_ISIZE) &&
+ // FIXME: Bug 1715681. Should we use HasReplacedSizing instead
+ // because IsReplaced is set on some other frames which are
+ // non-replaced elements, e.g. <select>?
+ aFrame->IsReplaced()) {
+ // This is the 'min-width/height:auto' "transferred size" piece of:
+ // https://drafts.csswg.org/css-flexbox-1/#min-size-auto
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ // Per spec, we handle it only for replaced elements.
+ result = std::min(result, minContentSize);
+ }
+ }
+ }
+ }
+
+ if (aFrame->IsTableFrame()) {
+ // Tables can't shrink smaller than their intrinsic minimum width,
+ // no matter what.
+ min = aFrame->GetMinISize(aRenderingContext);
+ }
+
+ // If we have an aspect-ratio and a definite block size of |aFrame|, we
+ // resolve the {min|max}-content size by the aspect-ratio and the block size.
+ // If |aAxis| is not the inline axis of |aFrame|, {min|max}-content should
+ // behaves as auto, so we don't need this.
+ //
+ // FIXME(emilio): For -moz-available it seems we shouldn't need this.
+ //
+ // https://github.com/w3c/csswg-drafts/issues/5032
+ // FIXME: Bug 1670151: Use GetAspectRatio() to cover replaced elements (and
+ // then we can drop the check of eSupportsAspectRatio).
+ const AspectRatio ar = stylePos->mAspectRatio.ToLayoutRatio();
+ if (isInlineAxis && ar && nsIFrame::ToExtremumLength(styleISize) &&
+ aFrame->SupportsAspectRatio() && !inlineSizeFromAspectRatio) {
+ // This 'B' in |styleBSize| means the block size of |aFrame|. We go into
+ // this branch only if |aAxis| is the inline axis of |aFrame|.
+ const StyleSize& styleBSize =
+ horizontalAxis ? stylePos->mHeight : stylePos->mWidth;
+ nscoord bSize;
+ if (GetDefiniteSize(styleBSize, aFrame, !isInlineAxis, aPercentageBasis,
+ &bSize) ||
+ (aPercentageBasis.isNothing() &&
+ GetPercentBSize(styleBSize, aFrame, horizontalAxis, bSize))) {
+ // We cannot reuse |boxSizing| because it may be updated to content-box
+ // in the above if-branch.
+ const StyleBoxSizing boxSizingForAR = stylePos->mBoxSizing;
+ if (!contentBoxSizeToBoxSizingAdjust) {
+ contentBoxSizeToBoxSizingAdjust.emplace(
+ getContentBoxSizeToBoxSizingAdjust(boxSizingForAR));
+ }
+ nscoord bSizeTakenByBoxSizing =
+ GetDefiniteSizeTakenByBoxSizing(boxSizingForAR, aFrame, !isInlineAxis,
+ ignorePadding, aPercentageBasis);
+ bSize -= bSizeTakenByBoxSizing;
+ inlineSizeFromAspectRatio.emplace(ar.ComputeRatioDependentSize(
+ LogicalAxis::eLogicalAxisInline, childWM, bSize,
+ *contentBoxSizeToBoxSizingAdjust));
+ }
+ }
+
+ nscoord contentBoxSize = result;
+ result = AddIntrinsicSizeOffset(
+ aRenderingContext, aFrame, offsets, aType, boxSizing, result, min,
+ styleISize, haveFixedMinISize ? &minISize : nullptr, styleMinISize,
+ haveFixedMaxISize ? &maxISize : nullptr, styleMaxISize,
+ inlineSizeFromAspectRatio, aFlags, aAxis);
+ nscoord overflow = result - aMarginBoxMinSizeClamp;
+ if (MOZ_UNLIKELY(overflow > 0)) {
+ nscoord newContentBoxSize = std::max(nscoord(0), contentBoxSize - overflow);
+ result -= contentBoxSize - newContentBoxSize;
+ }
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s %s intrinsic size for container is %d twips.\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref",
+ horizontalAxis ? "horizontal" : "vertical", result);
+#endif
+
+ return result;
+}
+
+/* static */
+nscoord nsLayoutUtils::IntrinsicForContainer(gfxContext* aRenderingContext,
+ nsIFrame* aFrame,
+ IntrinsicISizeType aType,
+ uint32_t aFlags) {
+ MOZ_ASSERT(aFrame && aFrame->GetParent());
+ // We want the size aFrame will contribute to its parent's inline-size.
+ PhysicalAxis axis =
+ aFrame->GetParent()->GetWritingMode().PhysicalAxis(eLogicalAxisInline);
+ return IntrinsicForAxis(axis, aRenderingContext, aFrame, aType, Nothing(),
+ aFlags);
+}
+
+/* static */
+nscoord nsLayoutUtils::MinSizeContributionForAxis(
+ PhysicalAxis aAxis, gfxContext* aRC, nsIFrame* aFrame,
+ IntrinsicISizeType aType, const LogicalSize& aPercentageBasis,
+ uint32_t aFlags) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->IsFlexOrGridItem(),
+ "only grid/flex items have this behavior currently");
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s min-isize for %s WM:\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref",
+ aAxis == eAxisVertical ? "vertical" : "horizontal");
+#endif
+
+ // Note: this method is only meant for grid/flex items.
+ const nsStylePosition* const stylePos = aFrame->StylePosition();
+ StyleSize size =
+ aAxis == eAxisHorizontal ? stylePos->mMinWidth : stylePos->mMinHeight;
+ StyleMaxSize maxSize =
+ aAxis == eAxisHorizontal ? stylePos->mMaxWidth : stylePos->mMaxHeight;
+ auto childWM = aFrame->GetWritingMode();
+ PhysicalAxis ourInlineAxis = childWM.PhysicalAxis(eLogicalAxisInline);
+ // According to the spec, max-content and min-content should behave as the
+ // property's initial values in block axis.
+ // It also make senses to use the initial values for -moz-fit-content and
+ // -moz-available for intrinsic size in block axis. Therefore, we reset them
+ // if needed.
+ if (aAxis != ourInlineAxis) {
+ if (size.BehavesLikeInitialValueOnBlockAxis()) {
+ size = StyleSize::Auto();
+ }
+ if (maxSize.BehavesLikeInitialValueOnBlockAxis()) {
+ maxSize = StyleMaxSize::None();
+ }
+ }
+
+ nscoord minSize;
+ nscoord* fixedMinSize = nullptr;
+ if (size.IsAuto()) {
+ if (aFrame->StyleDisplay()->IsScrollableOverflow()) {
+ // min-[width|height]:auto with scrollable overflow computes to
+ // zero.
+ minSize = 0;
+ fixedMinSize = &minSize;
+ } else {
+ size = aAxis == eAxisHorizontal ? stylePos->mWidth : stylePos->mHeight;
+ // This is same as above: keywords should behaves as property's initial
+ // values in block axis.
+ if (aAxis != ourInlineAxis && size.BehavesLikeInitialValueOnBlockAxis()) {
+ size = StyleSize::Auto();
+ }
+
+ if (GetAbsoluteCoord(size, minSize)) {
+ // We have a definite width/height. This is the "specified size" in:
+ // https://drafts.csswg.org/css-grid/#min-size-auto
+ fixedMinSize = &minSize;
+ } else if (aFrame->IsPercentageResolvedAgainstZero(size, maxSize)) {
+ // XXX bug 1463700: this doesn't handle calc() according to spec
+ minSize = 0;
+ fixedMinSize = &minSize;
+ }
+ // fall through - the caller will have to deal with "transferred size"
+ }
+ } else if (GetAbsoluteCoord(size, minSize)) {
+ fixedMinSize = &minSize;
+ } else if (size.IsLengthPercentage()) {
+ MOZ_ASSERT(size.HasPercent());
+ minSize = 0;
+ fixedMinSize = &minSize;
+ }
+
+ if (!fixedMinSize) {
+ // Let the caller deal with the "content size" cases.
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s min-isize is indefinite.\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref");
+#endif
+ return NS_UNCONSTRAINEDSIZE;
+ }
+
+ // If aFrame is a container for font size inflation, then shrink
+ // wrapping inside of it should not apply font size inflation.
+ AutoMaybeDisableFontInflation an(aFrame);
+
+ // The padding/margin percentage basis is the inline-size in the parent's
+ // writing-mode.
+ nscoord pmPercentageBasis =
+ aFrame->GetParent()->GetWritingMode().IsOrthogonalTo(childWM)
+ ? aPercentageBasis.BSize(childWM)
+ : aPercentageBasis.ISize(childWM);
+ nsIFrame::IntrinsicSizeOffsetData offsets =
+ ourInlineAxis == aAxis ? aFrame->IntrinsicISizeOffsets(pmPercentageBasis)
+ : aFrame->IntrinsicBSizeOffsets(pmPercentageBasis);
+ nscoord result = 0;
+ nscoord min = 0;
+ // Note: aInlineSizeFromAspectRatio is Nothing() here because we don't handle
+ // "content size" cases here (i.e. |fixedMinSize| is false here).
+ result = AddIntrinsicSizeOffset(
+ aRC, aFrame, offsets, aType, stylePos->mBoxSizing, result, min, size,
+ fixedMinSize, size, nullptr, maxSize, Nothing(), aFlags, aAxis);
+
+#ifdef DEBUG_INTRINSIC_WIDTH
+ nsIFrame::IndentBy(stderr, gNoiseIndent);
+ aFrame->ListTag(stderr);
+ printf_stderr(" %s min-isize is %d twips.\n",
+ aType == IntrinsicISizeType::MinISize ? "min" : "pref", result);
+#endif
+
+ return result;
+}
+
+/* static */
+nscoord nsLayoutUtils::ComputeBSizeDependentValue(
+ nscoord aContainingBlockBSize, const LengthPercentageOrAuto& aCoord) {
+ // XXXldb Some callers explicitly check aContainingBlockBSize
+ // against NS_UNCONSTRAINEDSIZE *and* unit against eStyleUnit_Percent or
+ // calc()s containing percents before calling this function.
+ // However, it would be much more likely to catch problems without
+ // the unit conditions.
+ // XXXldb Many callers pass a non-'auto' containing block height when
+ // according to CSS2.1 they should be passing 'auto'.
+ NS_ASSERTION(
+ NS_UNCONSTRAINEDSIZE != aContainingBlockBSize || !aCoord.HasPercent(),
+ "unexpected containing block block-size");
+
+ if (aCoord.IsAuto()) {
+ return 0;
+ }
+
+ return aCoord.AsLengthPercentage().Resolve(aContainingBlockBSize);
+}
+
+/* static */
+void nsLayoutUtils::MarkDescendantsDirty(nsIFrame* aSubtreeRoot) {
+ AutoTArray<nsIFrame*, 4> subtrees;
+ subtrees.AppendElement(aSubtreeRoot);
+
+ // dirty descendants, iterating over subtrees that may include
+ // additional subtrees associated with placeholders
+ do {
+ nsIFrame* subtreeRoot = subtrees.PopLastElement();
+
+ // Mark all descendants dirty (using an nsTArray stack rather than
+ // recursion).
+ // Note that ReflowInput::InitResizeFlags has some similar
+ // code; see comments there for how and why it differs.
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(subtreeRoot);
+
+ do {
+ nsIFrame* f = stack.PopLastElement();
+
+ f->MarkIntrinsicISizesDirty();
+
+ if (f->IsPlaceholderFrame()) {
+ nsIFrame* oof = nsPlaceholderFrame::GetRealFrameForPlaceholder(f);
+ if (!nsLayoutUtils::IsProperAncestorFrame(subtreeRoot, oof)) {
+ // We have another distinct subtree we need to mark.
+ subtrees.AppendElement(oof);
+ }
+ }
+
+ for (const auto& childList : f->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+ } while (subtrees.Length() != 0);
+}
+
+/* static */
+void nsLayoutUtils::MarkIntrinsicISizesDirtyIfDependentOnBSize(
+ nsIFrame* aFrame) {
+ AutoTArray<nsIFrame*, 32> stack;
+ stack.AppendElement(aFrame);
+
+ do {
+ nsIFrame* f = stack.PopLastElement();
+
+ if (!f->HasAnyStateBits(
+ NS_FRAME_DESCENDANT_INTRINSIC_ISIZE_DEPENDS_ON_BSIZE)) {
+ continue;
+ }
+ f->MarkIntrinsicISizesDirty();
+
+ for (const auto& childList : f->ChildLists()) {
+ for (nsIFrame* kid : childList.mList) {
+ stack.AppendElement(kid);
+ }
+ }
+ } while (stack.Length() != 0);
+}
+
+nsSize nsLayoutUtils::ComputeAutoSizeWithIntrinsicDimensions(
+ nscoord minWidth, nscoord minHeight, nscoord maxWidth, nscoord maxHeight,
+ nscoord tentWidth, nscoord tentHeight) {
+ // Now apply min/max-width/height - CSS 2.1 sections 10.4 and 10.7:
+
+ if (minWidth > maxWidth) maxWidth = minWidth;
+ if (minHeight > maxHeight) maxHeight = minHeight;
+
+ nscoord heightAtMaxWidth, heightAtMinWidth, widthAtMaxHeight,
+ widthAtMinHeight;
+
+ if (tentWidth > 0) {
+ heightAtMaxWidth = NSCoordMulDiv(maxWidth, tentHeight, tentWidth);
+ if (heightAtMaxWidth < minHeight) heightAtMaxWidth = minHeight;
+ heightAtMinWidth = NSCoordMulDiv(minWidth, tentHeight, tentWidth);
+ if (heightAtMinWidth > maxHeight) heightAtMinWidth = maxHeight;
+ } else {
+ heightAtMaxWidth = heightAtMinWidth =
+ NS_CSS_MINMAX(tentHeight, minHeight, maxHeight);
+ }
+
+ if (tentHeight > 0) {
+ widthAtMaxHeight = NSCoordMulDiv(maxHeight, tentWidth, tentHeight);
+ if (widthAtMaxHeight < minWidth) widthAtMaxHeight = minWidth;
+ widthAtMinHeight = NSCoordMulDiv(minHeight, tentWidth, tentHeight);
+ if (widthAtMinHeight > maxWidth) widthAtMinHeight = maxWidth;
+ } else {
+ widthAtMaxHeight = widthAtMinHeight =
+ NS_CSS_MINMAX(tentWidth, minWidth, maxWidth);
+ }
+
+ // The table at http://www.w3.org/TR/CSS21/visudet.html#min-max-widths :
+
+ nscoord width, height;
+
+ if (tentWidth > maxWidth) {
+ if (tentHeight > maxHeight) {
+ if (int64_t(maxWidth) * int64_t(tentHeight) <=
+ int64_t(maxHeight) * int64_t(tentWidth)) {
+ width = maxWidth;
+ height = heightAtMaxWidth;
+ } else {
+ width = widthAtMaxHeight;
+ height = maxHeight;
+ }
+ } else {
+ // This also covers "(w > max-width) and (h < min-height)" since in
+ // that case (max-width/w < 1), and with (h < min-height):
+ // max(max-width * h/w, min-height) == min-height
+ width = maxWidth;
+ height = heightAtMaxWidth;
+ }
+ } else if (tentWidth < minWidth) {
+ if (tentHeight < minHeight) {
+ if (int64_t(minWidth) * int64_t(tentHeight) <=
+ int64_t(minHeight) * int64_t(tentWidth)) {
+ width = widthAtMinHeight;
+ height = minHeight;
+ } else {
+ width = minWidth;
+ height = heightAtMinWidth;
+ }
+ } else {
+ // This also covers "(w < min-width) and (h > max-height)" since in
+ // that case (min-width/w > 1), and with (h > max-height):
+ // min(min-width * h/w, max-height) == max-height
+ width = minWidth;
+ height = heightAtMinWidth;
+ }
+ } else {
+ if (tentHeight > maxHeight) {
+ width = widthAtMaxHeight;
+ height = maxHeight;
+ } else if (tentHeight < minHeight) {
+ width = widthAtMinHeight;
+ height = minHeight;
+ } else {
+ width = tentWidth;
+ height = tentHeight;
+ }
+ }
+
+ return nsSize(width, height);
+}
+
+/* static */
+nscoord nsLayoutUtils::MinISizeFromInline(nsIFrame* aFrame,
+ gfxContext* aRenderingContext) {
+ NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
+ "should not be container for font size inflation");
+
+ nsIFrame::InlineMinISizeData data;
+ DISPLAY_MIN_INLINE_SIZE(aFrame, data.mPrevLines);
+ aFrame->AddInlineMinISize(aRenderingContext, &data);
+ data.ForceBreak();
+ return data.mPrevLines;
+}
+
+/* static */
+nscoord nsLayoutUtils::PrefISizeFromInline(nsIFrame* aFrame,
+ gfxContext* aRenderingContext) {
+ NS_ASSERTION(!aFrame->IsContainerForFontSizeInflation(),
+ "should not be container for font size inflation");
+
+ nsIFrame::InlinePrefISizeData data;
+ DISPLAY_PREF_INLINE_SIZE(aFrame, data.mPrevLines);
+ aFrame->AddInlinePrefISize(aRenderingContext, &data);
+ data.ForceBreak();
+ return data.mPrevLines;
+}
+
+static nscolor DarkenColor(nscolor aColor) {
+ uint16_t hue, sat, value;
+ uint8_t alpha;
+
+ // convert the RBG to HSV so we can get the lightness (which is the v)
+ NS_RGB2HSV(aColor, hue, sat, value, alpha);
+
+ // The goal here is to send white to black while letting colored
+ // stuff stay colored... So we adopt the following approach.
+ // Something with sat = 0 should end up with value = 0. Something
+ // with a high sat can end up with a high value and it's ok.... At
+ // the same time, we don't want to make things lighter. Do
+ // something simple, since it seems to work.
+ if (value > sat) {
+ value = sat;
+ // convert this color back into the RGB color space.
+ NS_HSV2RGB(aColor, hue, sat, value, alpha);
+ }
+ return aColor;
+}
+
+// Check whether we should darken text/decoration colors. We need to do this if
+// background images and colors are being suppressed, because that means
+// light text will not be visible against the (presumed light-colored)
+// background.
+static bool ShouldDarkenColors(nsIFrame* aFrame) {
+ nsPresContext* pc = aFrame->PresContext();
+ if (pc->GetBackgroundColorDraw() || pc->GetBackgroundImageDraw()) {
+ return false;
+ }
+ return aFrame->StyleVisibility()->mPrintColorAdjust !=
+ StylePrintColorAdjust::Exact;
+}
+
+nscolor nsLayoutUtils::DarkenColorIfNeeded(nsIFrame* aFrame, nscolor aColor) {
+ return ShouldDarkenColors(aFrame) ? DarkenColor(aColor) : aColor;
+}
+
+gfxFloat nsLayoutUtils::GetSnappedBaselineY(nsIFrame* aFrame,
+ gfxContext* aContext, nscoord aY,
+ nscoord aAscent) {
+ gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxFloat baseline = gfxFloat(aY) + aAscent;
+ gfxRect putativeRect(0, baseline / appUnitsPerDevUnit, 1, 1);
+ if (!aContext->UserToDevicePixelSnapped(
+ putativeRect, gfxContext::SnapOption::IgnoreScale)) {
+ return baseline;
+ }
+ return aContext->DeviceToUser(putativeRect.TopLeft()).y * appUnitsPerDevUnit;
+}
+
+gfxFloat nsLayoutUtils::GetSnappedBaselineX(nsIFrame* aFrame,
+ gfxContext* aContext, nscoord aX,
+ nscoord aAscent) {
+ gfxFloat appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
+ gfxFloat baseline = gfxFloat(aX) + aAscent;
+ gfxRect putativeRect(baseline / appUnitsPerDevUnit, 0, 1, 1);
+ if (!aContext->UserToDevicePixelSnapped(
+ putativeRect, gfxContext::SnapOption::IgnoreScale)) {
+ return baseline;
+ }
+ return aContext->DeviceToUser(putativeRect.TopLeft()).x * appUnitsPerDevUnit;
+}
+
+// Hard limit substring lengths to 8000 characters ... this lets us statically
+// size the cluster buffer array in FindSafeLength
+#define MAX_GFX_TEXT_BUF_SIZE 8000
+
+static int32_t FindSafeLength(const char16_t* aString, uint32_t aLength,
+ uint32_t aMaxChunkLength) {
+ if (aLength <= aMaxChunkLength) return aLength;
+
+ int32_t len = aMaxChunkLength;
+
+ // Ensure that we don't break inside a surrogate pair
+ while (len > 0 && NS_IS_LOW_SURROGATE(aString[len])) {
+ len--;
+ }
+ if (len == 0) {
+ // We don't want our caller to go into an infinite loop, so don't
+ // return zero. It's hard to imagine how we could actually get here
+ // unless there are languages that allow clusters of arbitrary size.
+ // If there are and someone feeds us a 500+ character cluster, too
+ // bad.
+ return aMaxChunkLength;
+ }
+ return len;
+}
+
+static int32_t GetMaxChunkLength(nsFontMetrics& aFontMetrics) {
+ return std::min(aFontMetrics.GetMaxStringLength(), MAX_GFX_TEXT_BUF_SIZE);
+}
+
+nscoord nsLayoutUtils::AppUnitWidthOfString(const char16_t* aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ nscoord width = 0;
+ while (aLength > 0) {
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ width += aFontMetrics.GetWidth(aString, len, aDrawTarget);
+ aLength -= len;
+ aString += len;
+ }
+ return width;
+}
+
+nscoord nsLayoutUtils::AppUnitWidthOfStringBidi(const char16_t* aString,
+ uint32_t aLength,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ gfxContext& aContext) {
+ nsPresContext* presContext = aFrame->PresContext();
+ if (presContext->BidiEnabled()) {
+ mozilla::intl::BidiEmbeddingLevel level =
+ nsBidiPresUtils::BidiLevelFromStyle(aFrame->Style());
+ return nsBidiPresUtils::MeasureTextWidth(
+ aString, aLength, level, presContext, aContext, aFontMetrics);
+ }
+ aFontMetrics.SetTextRunRTL(false);
+ aFontMetrics.SetVertical(aFrame->GetWritingMode().IsVertical());
+ aFontMetrics.SetTextOrientation(aFrame->StyleVisibility()->mTextOrientation);
+ return nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
+ aContext.GetDrawTarget());
+}
+
+bool nsLayoutUtils::StringWidthIsGreaterThan(const nsString& aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget,
+ nscoord aWidth) {
+ const char16_t* string = aString.get();
+ uint32_t length = aString.Length();
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ nscoord width = 0;
+ while (length > 0) {
+ int32_t len = FindSafeLength(string, length, maxChunkLength);
+ width += aFontMetrics.GetWidth(string, len, aDrawTarget);
+ if (width > aWidth) {
+ return true;
+ }
+ length -= len;
+ string += len;
+ }
+ return false;
+}
+
+nsBoundingMetrics nsLayoutUtils::AppUnitBoundsOfString(
+ const char16_t* aString, uint32_t aLength, nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ // Assign directly in the first iteration. This ensures that
+ // negative ascent/descent can be returned and the left bearing
+ // is properly initialized.
+ nsBoundingMetrics totalMetrics =
+ aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
+ aLength -= len;
+ aString += len;
+
+ while (aLength > 0) {
+ len = FindSafeLength(aString, aLength, maxChunkLength);
+ nsBoundingMetrics metrics =
+ aFontMetrics.GetBoundingMetrics(aString, len, aDrawTarget);
+ totalMetrics += metrics;
+ aLength -= len;
+ aString += len;
+ }
+ return totalMetrics;
+}
+
+void nsLayoutUtils::DrawString(const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ gfxContext* aContext, const char16_t* aString,
+ int32_t aLength, nsPoint aPoint,
+ ComputedStyle* aComputedStyle,
+ DrawStringFlags aFlags) {
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // If caller didn't pass a style, use the frame's.
+ if (!aComputedStyle) {
+ aComputedStyle = aFrame->Style();
+ }
+
+ if (aFlags & DrawStringFlags::ForceHorizontal) {
+ aFontMetrics.SetVertical(false);
+ } else {
+ aFontMetrics.SetVertical(WritingMode(aComputedStyle).IsVertical());
+ }
+
+ aFontMetrics.SetTextOrientation(
+ aComputedStyle->StyleVisibility()->mTextOrientation);
+
+ nsPresContext* presContext = aFrame->PresContext();
+ if (presContext->BidiEnabled()) {
+ mozilla::intl::BidiEmbeddingLevel level =
+ nsBidiPresUtils::BidiLevelFromStyle(aComputedStyle);
+ rv = nsBidiPresUtils::RenderText(aString, aLength, level, presContext,
+ *aContext, aContext->GetDrawTarget(),
+ aFontMetrics, aPoint.x, aPoint.y);
+ }
+ if (NS_FAILED(rv)) {
+ aFontMetrics.SetTextRunRTL(false);
+ DrawUniDirString(aString, aLength, aPoint, aFontMetrics, *aContext);
+ }
+}
+
+void nsLayoutUtils::DrawUniDirString(const char16_t* aString, uint32_t aLength,
+ const nsPoint& aPoint,
+ nsFontMetrics& aFontMetrics,
+ gfxContext& aContext) {
+ nscoord x = aPoint.x;
+ nscoord y = aPoint.y;
+
+ uint32_t maxChunkLength = GetMaxChunkLength(aFontMetrics);
+ if (aLength <= maxChunkLength) {
+ aFontMetrics.DrawString(aString, aLength, x, y, &aContext,
+ aContext.GetDrawTarget());
+ return;
+ }
+
+ bool isRTL = aFontMetrics.GetTextRunRTL();
+
+ // If we're drawing right to left, we must start at the end.
+ if (isRTL) {
+ x += nsLayoutUtils::AppUnitWidthOfString(aString, aLength, aFontMetrics,
+ aContext.GetDrawTarget());
+ }
+
+ while (aLength > 0) {
+ int32_t len = FindSafeLength(aString, aLength, maxChunkLength);
+ nscoord width =
+ aFontMetrics.GetWidth(aString, len, aContext.GetDrawTarget());
+ if (isRTL) {
+ x -= width;
+ }
+ aFontMetrics.DrawString(aString, len, x, y, &aContext,
+ aContext.GetDrawTarget());
+ if (!isRTL) {
+ x += width;
+ }
+ aLength -= len;
+ aString += len;
+ }
+}
+
+/* static */
+void nsLayoutUtils::PaintTextShadow(
+ const nsIFrame* aFrame, gfxContext* aContext, const nsRect& aTextRect,
+ const nsRect& aDirtyRect, const nscolor& aForegroundColor,
+ TextShadowCallback aCallback, void* aCallbackData) {
+ const nsStyleText* textStyle = aFrame->StyleText();
+ auto shadows = textStyle->mTextShadow.AsSpan();
+ if (shadows.IsEmpty()) {
+ return;
+ }
+
+ // Text shadow happens with the last value being painted at the back,
+ // ie. it is painted first.
+ gfxContext* aDestCtx = aContext;
+ for (auto& shadow : Reversed(shadows)) {
+ nsPoint shadowOffset(shadow.horizontal.ToAppUnits(),
+ shadow.vertical.ToAppUnits());
+ nscoord blurRadius = std::max(shadow.blur.ToAppUnits(), 0);
+
+ nsRect shadowRect(aTextRect);
+ shadowRect.MoveBy(shadowOffset);
+
+ nsPresContext* presCtx = aFrame->PresContext();
+ nsContextBoxBlur contextBoxBlur;
+
+ nscolor shadowColor = shadow.color.CalcColor(aForegroundColor);
+
+ // Webrender just needs the shadow details
+ if (auto* textDrawer = aContext->GetTextDrawer()) {
+ wr::Shadow wrShadow;
+
+ wrShadow.offset = {
+ presCtx->AppUnitsToFloatDevPixels(shadow.horizontal.ToAppUnits()),
+ presCtx->AppUnitsToFloatDevPixels(shadow.vertical.ToAppUnits())};
+
+ wrShadow.blur_radius = presCtx->AppUnitsToFloatDevPixels(blurRadius);
+ wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
+
+ // Gecko already inflates the bounding rect of text shadows,
+ // so tell WR not to inflate again.
+ bool inflate = false;
+ textDrawer->AppendShadow(wrShadow, inflate);
+ continue;
+ }
+
+ gfxContext* shadowContext = contextBoxBlur.Init(
+ shadowRect, 0, blurRadius, presCtx->AppUnitsPerDevPixel(), aDestCtx,
+ aDirtyRect, nullptr,
+ nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR);
+ if (!shadowContext) continue;
+
+ aDestCtx->Save();
+ aDestCtx->NewPath();
+ aDestCtx->SetColor(sRGBColor::FromABGR(shadowColor));
+
+ // The callback will draw whatever we want to blur as a shadow.
+ aCallback(shadowContext, shadowOffset, shadowColor, aCallbackData);
+
+ contextBoxBlur.DoPaint();
+ aDestCtx->Restore();
+ }
+}
+
+/* static */
+nscoord nsLayoutUtils::GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
+ nscoord aLineHeight,
+ bool aIsInverted) {
+ nscoord fontAscent =
+ aIsInverted ? aFontMetrics->MaxDescent() : aFontMetrics->MaxAscent();
+ nscoord fontHeight = aFontMetrics->MaxHeight();
+
+ nscoord leading = aLineHeight - fontHeight;
+ return fontAscent + leading / 2;
+}
+
+/* static */
+bool nsLayoutUtils::GetFirstLineBaseline(WritingMode aWritingMode,
+ const nsIFrame* aFrame,
+ nscoord* aResult) {
+ LinePosition position;
+ if (!GetFirstLinePosition(aWritingMode, aFrame, &position)) return false;
+ *aResult = position.mBaseline;
+ return true;
+}
+
+/* static */
+bool nsLayoutUtils::GetFirstLinePosition(WritingMode aWM,
+ const nsIFrame* aFrame,
+ LinePosition* aResult) {
+ if (aFrame->StyleDisplay()->IsContainLayout()) {
+ return false;
+ }
+ const nsBlockFrame* block = do_QueryFrame(aFrame);
+ if (!block) {
+ // For the first-line baseline we also have to check for a table, and if
+ // so, use the baseline of its first row.
+ LayoutFrameType fType = aFrame->Type();
+ if (fType == LayoutFrameType::TableWrapper ||
+ fType == LayoutFrameType::FlexContainer ||
+ fType == LayoutFrameType::GridContainer) {
+ if ((fType == LayoutFrameType::GridContainer &&
+ aFrame->HasAnyStateBits(NS_STATE_GRID_SYNTHESIZE_BASELINE)) ||
+ (fType == LayoutFrameType::FlexContainer &&
+ aFrame->HasAnyStateBits(NS_STATE_FLEX_SYNTHESIZE_BASELINE)) ||
+ (fType == LayoutFrameType::TableWrapper &&
+ static_cast<const nsTableWrapperFrame*>(aFrame)->GetRowCount() ==
+ 0)) {
+ // empty grid/flex/table container
+ aResult->mBStart = 0;
+ aResult->mBaseline = Baseline::SynthesizeBOffsetFromBorderBox(
+ aFrame, aWM, BaselineSharingGroup::First);
+ aResult->mBEnd = aFrame->BSize(aWM);
+ return true;
+ }
+ if (fType == LayoutFrameType::TableWrapper &&
+ aFrame->GetWritingMode().IsOrthogonalTo(aWM)) {
+ // For tables, the upcoming GetLogicalBaseline call would determine the
+ // table's baseline from its first row that has a baseline. However:
+ // this doesn't make sense for an orthogonal writing mode, so in that
+ // case we report no baseline instead. The table wrapper and its rows
+ // should flow the same way, so we can bail out early, but this logic
+ // wouldn't be correct to transplant into other places in the codebase
+ // (Depending on how bug 1786633 is resolved).
+ return false;
+ }
+ aResult->mBStart = 0;
+ aResult->mBaseline = aFrame->GetLogicalBaseline(aWM);
+ // This is what we want for the list bullet caller; not sure if
+ // other future callers will want the same.
+ aResult->mBEnd = aFrame->BSize(aWM);
+ return true;
+ }
+
+ // For first-line baselines, we have to consider scroll frames.
+ if (nsIScrollableFrame* sFrame =
+ do_QueryFrame(const_cast<nsIFrame*>(aFrame))) {
+ LinePosition kidPosition;
+ if (GetFirstLinePosition(aWM, sFrame->GetScrolledFrame(), &kidPosition)) {
+ // Consider only the border (Padding is ignored, since
+ // `-moz-scrolled-content` inherits and handles the padding) that
+ // contributes to the kid's position, not the scrolling, so we get the
+ // initial position.
+ *aResult = kidPosition + aFrame->GetLogicalUsedBorder(aWM).BStart(aWM);
+ // Don't want to move the line's block positioning, but the baseline
+ // needs to be clamped (See bug 1791069).
+ aResult->mBaseline = std::clamp(aResult->mBaseline, 0,
+ aFrame->GetLogicalSize(aWM).BSize(aWM));
+ return true;
+ }
+ return false;
+ }
+
+ if (fType == LayoutFrameType::FieldSet) {
+ LinePosition kidPosition;
+ // Get the first baseline from the fieldset content, not from the legend.
+ nsIFrame* kid = static_cast<const nsFieldSetFrame*>(aFrame)->GetInner();
+ if (kid && GetFirstLinePosition(aWM, kid, &kidPosition)) {
+ *aResult = kidPosition +
+ kid->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
+ return true;
+ }
+ return false;
+ }
+
+ if (fType == LayoutFrameType::ColumnSet) {
+ // Note(dshin): This is basically the same as
+ // `nsColumnSetFrame::GetNaturalBaselineBOffset`, but with line start and
+ // end, all stored in `LinePosition`. Field value apart from baseline is
+ // used in one other place
+ // (`nsBlockFrame`) - if that goes away, this becomes a duplication that
+ // should be removed.
+ LinePosition kidPosition;
+ for (const auto* kid : aFrame->PrincipalChildList()) {
+ LinePosition position;
+ if (!GetFirstLinePosition(aWM, kid, &position)) {
+ continue;
+ }
+ if (position.mBaseline < kidPosition.mBaseline) {
+ kidPosition = position;
+ }
+ }
+ if (kidPosition.mBaseline != nscoord_MAX) {
+ *aResult = kidPosition;
+ return true;
+ }
+ }
+
+ // No baseline.
+ return false;
+ }
+
+ for (const auto& line : block->Lines()) {
+ if (line.IsBlock()) {
+ const nsIFrame* kid = line.mFirstChild;
+ LinePosition kidPosition;
+ if (GetFirstLinePosition(aWM, kid, &kidPosition)) {
+ // XXX Not sure if this is the correct value to use for container
+ // width here. It will only be used in vertical-rl layout,
+ // which we don't have full support and testing for yet.
+ const auto& containerSize = line.mContainerSize;
+ *aResult = kidPosition +
+ kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ return true;
+ }
+ } else {
+ // XXX Is this the right test? We have some bogus empty lines
+ // floating around, but IsEmpty is perhaps too weak.
+ if (0 != line.BSize() || !line.IsEmpty()) {
+ nscoord bStart = line.BStart();
+ aResult->mBStart = bStart;
+ aResult->mBaseline = bStart + line.GetLogicalAscent();
+ aResult->mBEnd = bStart + line.BSize();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsLayoutUtils::GetLastLineBaseline(WritingMode aWM, const nsIFrame* aFrame,
+ nscoord* aResult) {
+ if (aFrame->StyleDisplay()->IsContainLayout()) {
+ return false;
+ }
+
+ const nsBlockFrame* block = do_QueryFrame(aFrame);
+ if (!block) {
+ if (nsIScrollableFrame* sFrame = do_QueryFrame(aFrame)) {
+ // Use the baseline position only if the last line's baseline is within
+ // the scrolling frame's box in the initial position.
+ const auto* scrolledFrame = sFrame->GetScrolledFrame();
+ if (!GetLastLineBaseline(aWM, scrolledFrame, aResult)) {
+ return false;
+ }
+ // Go from scrolled frame to scrollable frame position.
+ *aResult += aFrame->GetLogicalUsedBorder(aWM).BStart(aWM);
+ const auto maxBaseline = aFrame->GetLogicalSize(aWM).BSize(aWM);
+ // Clamp the last baseline to border (See bug 1791069).
+ *aResult = std::clamp(*aResult, 0, maxBaseline);
+ return true;
+ }
+
+ // No need to duplicate the baseline logic (Unlike `GetFirstLinePosition`,
+ // we don't need to return any other value apart from baseline), just defer
+ // to `GetNaturalBaselineBOffset`. Technically, we could do this at
+ // `ColumnSetWrapperFrame` level, but this keeps it symmetric to
+ // `GetFirstLinePosition`.
+ if (aFrame->IsColumnSetFrame()) {
+ const auto baseline = aFrame->GetNaturalBaselineBOffset(
+ aWM, BaselineSharingGroup::Last, BaselineExportContext::Other);
+ if (!baseline) {
+ return false;
+ }
+ *aResult = aFrame->BSize(aWM) - *baseline;
+ return true;
+ }
+ // No baseline.
+ return false;
+ }
+
+ for (nsBlockFrame::ConstReverseLineIterator line = block->LinesRBegin(),
+ line_end = block->LinesREnd();
+ line != line_end; ++line) {
+ if (line->IsBlock()) {
+ nsIFrame* kid = line->mFirstChild;
+ nscoord kidBaseline;
+ const nsSize& containerSize = line->mContainerSize;
+ if (GetLastLineBaseline(aWM, kid, &kidBaseline)) {
+ // Ignore relative positioning for baseline calculations
+ *aResult = kidBaseline +
+ kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ return true;
+ }
+ if (kid->IsScrollFrame()) {
+ // Defer to nsIFrame::GetLogicalBaseline (which synthesizes a baseline
+ // from the margin-box).
+ kidBaseline = kid->GetLogicalBaseline(aWM);
+ *aResult = kidBaseline +
+ kid->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ return true;
+ }
+ } else {
+ // 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()) {
+ *aResult = line->BStart() + line->GetLogicalAscent();
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static nscoord CalculateBlockContentBEnd(WritingMode aWM,
+ nsBlockFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ nscoord contentBEnd = 0;
+
+ for (const auto& line : aFrame->Lines()) {
+ if (line.IsBlock()) {
+ nsIFrame* child = line.mFirstChild;
+ const auto& containerSize = line.mContainerSize;
+ nscoord offset =
+ child->GetLogicalNormalPosition(aWM, containerSize).B(aWM);
+ contentBEnd =
+ std::max(contentBEnd,
+ nsLayoutUtils::CalculateContentBEnd(aWM, child) + offset);
+ } else {
+ contentBEnd = std::max(contentBEnd, line.BEnd());
+ }
+ }
+ return contentBEnd;
+}
+
+/* static */
+nscoord nsLayoutUtils::CalculateContentBEnd(WritingMode aWM, nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame, "null ptr");
+
+ nscoord contentBEnd = aFrame->BSize(aWM);
+
+ // We want scrollable overflow rather than visual because this
+ // calculation is intended to affect layout.
+ LogicalSize overflowSize(aWM, aFrame->ScrollableOverflowRect().Size());
+ if (overflowSize.BSize(aWM) > contentBEnd) {
+ FrameChildListIDs skip = {FrameChildListID::Overflow,
+ FrameChildListID::ExcessOverflowContainers,
+ FrameChildListID::OverflowOutOfFlow};
+ nsBlockFrame* blockFrame = do_QueryFrame(aFrame);
+ if (blockFrame) {
+ contentBEnd =
+ std::max(contentBEnd, CalculateBlockContentBEnd(aWM, blockFrame));
+ skip += FrameChildListID::Principal;
+ }
+ for (const auto& [list, listID] : aFrame->ChildLists()) {
+ if (!skip.contains(listID)) {
+ for (nsIFrame* child : list) {
+ nscoord offset =
+ child->GetLogicalNormalPosition(aWM, aFrame->GetSize()).B(aWM);
+ contentBEnd =
+ std::max(contentBEnd, CalculateContentBEnd(aWM, child) + offset);
+ }
+ }
+ }
+ }
+ return contentBEnd;
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetClosestLayer(nsIFrame* aFrame) {
+ nsIFrame* layer;
+ for (layer = aFrame; layer; layer = layer->GetParent()) {
+ if (layer->IsAbsPosContainingBlock() ||
+ (layer->GetParent() && layer->GetParent()->IsScrollFrame()))
+ break;
+ }
+ if (layer) return layer;
+ return aFrame->PresShell()->GetRootFrame();
+}
+
+SamplingFilter nsLayoutUtils::GetSamplingFilterForFrame(nsIFrame* aForFrame) {
+ switch (aForFrame->UsedImageRendering()) {
+ case StyleImageRendering::Smooth:
+ case StyleImageRendering::Optimizequality:
+ return SamplingFilter::LINEAR;
+ case StyleImageRendering::CrispEdges:
+ case StyleImageRendering::Optimizespeed:
+ case StyleImageRendering::Pixelated:
+ return SamplingFilter::POINT;
+ case StyleImageRendering::Auto:
+ return SamplingFilter::GOOD;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown image-rendering value");
+ return SamplingFilter::GOOD;
+}
+
+/**
+ * Given an image being drawn into an appunit coordinate system, and
+ * a point in that coordinate system, map the point back into image
+ * pixel space.
+ * @param aSize the size of the image, in pixels
+ * @param aDest the rectangle that the image is being mapped into
+ * @param aPt a point in the same coordinate system as the rectangle
+ */
+static gfxPoint MapToFloatImagePixels(const gfxSize& aSize,
+ const gfxRect& aDest,
+ const gfxPoint& aPt) {
+ return gfxPoint(((aPt.x - aDest.X()) * aSize.width) / aDest.Width(),
+ ((aPt.y - aDest.Y()) * aSize.height) / aDest.Height());
+}
+
+/**
+ * Given an image being drawn into an pixel-based coordinate system, and
+ * a point in image space, map the point into the pixel-based coordinate
+ * system.
+ * @param aSize the size of the image, in pixels
+ * @param aDest the rectangle that the image is being mapped into
+ * @param aPt a point in image space
+ */
+static gfxPoint MapToFloatUserPixels(const gfxSize& aSize, const gfxRect& aDest,
+ const gfxPoint& aPt) {
+ return gfxPoint(aPt.x * aDest.Width() / aSize.width + aDest.X(),
+ aPt.y * aDest.Height() / aSize.height + aDest.Y());
+}
+
+/* static */
+gfxRect nsLayoutUtils::RectToGfxRect(const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel) {
+ return gfxRect(gfxFloat(aRect.x) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.y) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.width) / aAppUnitsPerDevPixel,
+ gfxFloat(aRect.height) / aAppUnitsPerDevPixel);
+}
+
+struct SnappedImageDrawingParameters {
+ // A transform from image space to device space.
+ gfxMatrix imageSpaceToDeviceSpace;
+ // The size at which the image should be drawn (which may not be its
+ // intrinsic size due to, for example, HQ scaling).
+ nsIntSize size;
+ // The region in tiled image space which will be drawn, with an associated
+ // region to which sampling should be restricted.
+ ImageRegion region;
+ // The default viewport size for SVG images, which we use unless a different
+ // one has been explicitly specified. This is the same as |size| except that
+ // it does not take into account any transformation on the gfxContext we're
+ // drawing to - for example, CSS transforms are not taken into account.
+ CSSIntSize svgViewportSize;
+ // Whether there's anything to draw at all.
+ bool shouldDraw;
+
+ SnappedImageDrawingParameters()
+ : region(ImageRegion::Empty()), shouldDraw(false) {}
+
+ SnappedImageDrawingParameters(const gfxMatrix& aImageSpaceToDeviceSpace,
+ const nsIntSize& aSize,
+ const ImageRegion& aRegion,
+ const CSSIntSize& aSVGViewportSize)
+ : imageSpaceToDeviceSpace(aImageSpaceToDeviceSpace),
+ size(aSize),
+ region(aRegion),
+ svgViewportSize(aSVGViewportSize),
+ shouldDraw(true) {}
+};
+
+/**
+ * Given two axis-aligned rectangles, returns the transformation that maps the
+ * first onto the second.
+ *
+ * @param aFrom The rect to be transformed.
+ * @param aTo The rect that aFrom should be mapped onto by the transformation.
+ */
+static gfxMatrix TransformBetweenRects(const gfxRect& aFrom,
+ const gfxRect& aTo) {
+ MatrixScalesDouble scale(aTo.width / aFrom.width, aTo.height / aFrom.height);
+ gfxPoint translation(aTo.x - aFrom.x * scale.xScale,
+ aTo.y - aFrom.y * scale.yScale);
+ return gfxMatrix(scale.xScale, 0, 0, scale.yScale, translation.x,
+ translation.y);
+}
+
+static nsRect TileNearRect(const nsRect& aAnyTile, const nsRect& aTargetRect) {
+ nsPoint distance = aTargetRect.TopLeft() - aAnyTile.TopLeft();
+ return aAnyTile + nsPoint(distance.x / aAnyTile.width * aAnyTile.width,
+ distance.y / aAnyTile.height * aAnyTile.height);
+}
+
+static gfxFloat StableRound(gfxFloat aValue) {
+ // Values slightly less than 0.5 should round up like 0.5 would; we're
+ // assuming they were meant to be 0.5.
+ return floor(aValue + 0.5001);
+}
+
+static gfxPoint StableRound(const gfxPoint& aPoint) {
+ return gfxPoint(StableRound(aPoint.x), StableRound(aPoint.y));
+}
+
+/**
+ * Given a set of input parameters, compute certain output parameters
+ * for drawing an image with the image snapping algorithm.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ *
+ * @see nsLayoutUtils::DrawImage() for the descriptions of input parameters
+ */
+static SnappedImageDrawingParameters ComputeSnappedImageDrawingParameters(
+ gfxContext* aCtx, int32_t aAppUnitsPerDevPixel, const nsRect aDest,
+ const nsRect aFill, const nsPoint aAnchor, const nsRect aDirty,
+ imgIContainer* aImage, const SamplingFilter aSamplingFilter,
+ uint32_t aImageFlags, ExtendMode aExtendMode) {
+ if (aDest.IsEmpty() || aFill.IsEmpty())
+ return SnappedImageDrawingParameters();
+
+ // Avoid unnecessarily large offsets.
+ bool doTile = !aDest.Contains(aFill);
+ nsRect appUnitDest =
+ doTile ? TileNearRect(aDest, aFill.Intersect(aDirty)) : aDest;
+ nsPoint anchor = aAnchor + (appUnitDest.TopLeft() - aDest.TopLeft());
+
+ gfxRect devPixelDest =
+ nsLayoutUtils::RectToGfxRect(appUnitDest, aAppUnitsPerDevPixel);
+ gfxRect devPixelFill =
+ nsLayoutUtils::RectToGfxRect(aFill, aAppUnitsPerDevPixel);
+ gfxRect devPixelDirty =
+ nsLayoutUtils::RectToGfxRect(aDirty, aAppUnitsPerDevPixel);
+
+ gfxMatrix currentMatrix = aCtx->CurrentMatrixDouble();
+ gfxRect fill = devPixelFill;
+ gfxRect dest = devPixelDest;
+ bool didSnap;
+ // Snap even if we have a scale in the context. But don't snap if
+ // we have something that's not translation+scale, or if the scale flips in
+ // the X or Y direction, because snapped image drawing can't handle that yet.
+ if (!currentMatrix.HasNonAxisAlignedTransform() && currentMatrix._11 > 0.0 &&
+ currentMatrix._22 > 0.0 &&
+ aCtx->UserToDevicePixelSnapped(fill,
+ gfxContext::SnapOption::IgnoreScale) &&
+ aCtx->UserToDevicePixelSnapped(dest,
+ gfxContext::SnapOption::IgnoreScale)) {
+ // We snapped. On this code path, |fill| and |dest| take into account
+ // currentMatrix's transform.
+ didSnap = true;
+ } else {
+ // We didn't snap. On this code path, |fill| and |dest| do not take into
+ // account currentMatrix's transform.
+ didSnap = false;
+ fill = devPixelFill;
+ dest = devPixelDest;
+ }
+
+ // If we snapped above, |dest| already takes into account |currentMatrix|'s
+ // scale and has integer coordinates. If not, we need these properties to
+ // compute the optimal drawn image size, so compute |snappedDestSize| here.
+ gfxSize snappedDestSize = dest.Size();
+ auto scaleFactors = currentMatrix.ScaleFactors();
+ if (!didSnap) {
+ snappedDestSize.Scale(scaleFactors.xScale, scaleFactors.yScale);
+ snappedDestSize.width = NS_round(snappedDestSize.width);
+ snappedDestSize.height = NS_round(snappedDestSize.height);
+ }
+
+ // We need to be sure that this is at least one pixel in width and height,
+ // or we'll end up drawing nothing even if we have a nonempty fill.
+ snappedDestSize.width = std::max(snappedDestSize.width, 1.0);
+ snappedDestSize.height = std::max(snappedDestSize.height, 1.0);
+
+ // Bail if we're not going to end up drawing anything.
+ if (fill.IsEmpty()) {
+ return SnappedImageDrawingParameters();
+ }
+
+ nsIntSize intImageSize = aImage->OptimalImageSizeForDest(
+ snappedDestSize, imgIContainer::FRAME_CURRENT, aSamplingFilter,
+ aImageFlags);
+
+ nsIntSize svgViewportSize;
+ if (scaleFactors.xScale == 1.0 && scaleFactors.yScale == 1.0) {
+ // intImageSize is scaled by currentMatrix. But since there are no scale
+ // factors in currentMatrix, it is safe to assign intImageSize to
+ // svgViewportSize directly.
+ svgViewportSize = intImageSize;
+ } else {
+ // We should not take into account any transformation of currentMatrix
+ // when computing svg viewport size. Since currentMatrix contains scale
+ // factors, we need to recompute SVG viewport by unscaled devPixelDest.
+ svgViewportSize = aImage->OptimalImageSizeForDest(
+ devPixelDest.Size(), imgIContainer::FRAME_CURRENT, aSamplingFilter,
+ aImageFlags);
+ }
+
+ gfxSize imageSize(intImageSize.width, intImageSize.height);
+
+ // Compute the set of pixels that would be sampled by an ideal rendering
+ gfxPoint subimageTopLeft =
+ MapToFloatImagePixels(imageSize, devPixelDest, devPixelFill.TopLeft());
+ gfxPoint subimageBottomRight = MapToFloatImagePixels(
+ imageSize, devPixelDest, devPixelFill.BottomRight());
+ gfxRect subimage;
+ subimage.MoveTo(NSToIntFloor(subimageTopLeft.x),
+ NSToIntFloor(subimageTopLeft.y));
+ subimage.SizeTo(NSToIntCeil(subimageBottomRight.x) - subimage.x,
+ NSToIntCeil(subimageBottomRight.y) - subimage.y);
+
+ if (subimage.IsEmpty()) {
+ // Bail if the subimage is empty (we're not going to be drawing anything).
+ return SnappedImageDrawingParameters();
+ }
+
+ gfxMatrix transform;
+ gfxMatrix invTransform;
+
+ bool anchorAtUpperLeft =
+ anchor.x == appUnitDest.x && anchor.y == appUnitDest.y;
+ bool exactlyOneImageCopy = aFill.IsEqualEdges(appUnitDest);
+ if (anchorAtUpperLeft && exactlyOneImageCopy) {
+ // The simple case: we can ignore the anchor point and compute the
+ // transformation from the sampled region (the subimage) to the fill rect.
+ // This approach is preferable when it works since it tends to produce
+ // less numerical error.
+ transform = TransformBetweenRects(subimage, fill);
+ invTransform = TransformBetweenRects(fill, subimage);
+ } else {
+ // The more complicated case: we compute the transformation from the
+ // image rect positioned at the image space anchor point to the dest rect
+ // positioned at the device space anchor point.
+
+ // Compute the anchor point in both device space and image space. This
+ // code assumes that pixel-based devices have one pixel per device unit!
+ gfxPoint anchorPoint(gfxFloat(anchor.x) / aAppUnitsPerDevPixel,
+ gfxFloat(anchor.y) / aAppUnitsPerDevPixel);
+ gfxPoint imageSpaceAnchorPoint =
+ MapToFloatImagePixels(imageSize, devPixelDest, anchorPoint);
+
+ if (didSnap) {
+ imageSpaceAnchorPoint = StableRound(imageSpaceAnchorPoint);
+ anchorPoint = imageSpaceAnchorPoint;
+ anchorPoint = MapToFloatUserPixels(imageSize, devPixelDest, anchorPoint);
+ anchorPoint = currentMatrix.TransformPoint(anchorPoint);
+ anchorPoint = StableRound(anchorPoint);
+ }
+
+ // Compute an unsnapped version of the dest rect's size. We continue to
+ // follow the pattern that we take |currentMatrix| into account only if
+ // |didSnap| is true.
+ gfxSize unsnappedDestSize =
+ didSnap ? devPixelDest.Size() * currentMatrix.ScaleFactors()
+ : devPixelDest.Size();
+
+ gfxRect anchoredDestRect(anchorPoint, unsnappedDestSize);
+ gfxRect anchoredImageRect(imageSpaceAnchorPoint, imageSize);
+
+ // Calculate anchoredDestRect with snapped fill rect when the devPixelFill
+ // rect corresponds to just a single tile in that direction
+ if (fill.Width() != devPixelFill.Width() &&
+ devPixelDest.x == devPixelFill.x &&
+ devPixelDest.XMost() == devPixelFill.XMost()) {
+ anchoredDestRect.width = fill.width;
+ }
+ if (fill.Height() != devPixelFill.Height() &&
+ devPixelDest.y == devPixelFill.y &&
+ devPixelDest.YMost() == devPixelFill.YMost()) {
+ anchoredDestRect.height = fill.height;
+ }
+
+ transform = TransformBetweenRects(anchoredImageRect, anchoredDestRect);
+ invTransform = TransformBetweenRects(anchoredDestRect, anchoredImageRect);
+ }
+
+ // If the transform is not a straight translation by integers, then
+ // filtering will occur, and restricting the fill rect to the dirty rect
+ // would change the values computed for edge pixels, which we can't allow.
+ // Also, if 'didSnap' is false then rounding out 'devPixelDirty' might not
+ // produce pixel-aligned coordinates, which would also break the values
+ // computed for edge pixels.
+ if (didSnap && !invTransform.HasNonIntegerTranslation()) {
+ // This form of Transform is safe to call since non-axis-aligned
+ // transforms wouldn't be snapped.
+ devPixelDirty = currentMatrix.TransformRect(devPixelDirty);
+ devPixelDirty.RoundOut();
+ fill = fill.Intersect(devPixelDirty);
+ }
+ if (fill.IsEmpty()) return SnappedImageDrawingParameters();
+
+ gfxRect imageSpaceFill(didSnap ? invTransform.TransformRect(fill)
+ : invTransform.TransformBounds(fill));
+
+ // If we didn't snap, we need to post-multiply the matrix on the context to
+ // get the final matrix we'll draw with, because we didn't take it into
+ // account when computing the matrices above.
+ if (!didSnap) {
+ transform = transform * currentMatrix;
+ }
+
+ ExtendMode extendMode = (aImageFlags & imgIContainer::FLAG_CLAMP)
+ ? ExtendMode::CLAMP
+ : aExtendMode;
+ // We were passed in the default extend mode but need to tile.
+ if (extendMode == ExtendMode::CLAMP && doTile) {
+ MOZ_ASSERT(!(aImageFlags & imgIContainer::FLAG_CLAMP));
+ extendMode = ExtendMode::REPEAT;
+ }
+
+ ImageRegion region = ImageRegion::CreateWithSamplingRestriction(
+ imageSpaceFill, subimage, extendMode);
+
+ return SnappedImageDrawingParameters(
+ transform, intImageSize, region,
+ CSSIntSize(svgViewportSize.width, svgViewportSize.height));
+}
+
+static ImgDrawResult DrawImageInternal(
+ gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter, const nsRect& aDest,
+ const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
+ const SVGImageContext& aSVGContext, uint32_t aImageFlags,
+ ExtendMode aExtendMode = ExtendMode::CLAMP, float aOpacity = 1.0) {
+ ImgDrawResult result = ImgDrawResult::SUCCESS;
+
+ aImageFlags |= imgIContainer::FLAG_ASYNC_NOTIFY;
+
+ if (aPresContext->Type() == nsPresContext::eContext_Print) {
+ // We want vector images to be passed on as vector commands, not a raster
+ // image.
+ aImageFlags |= imgIContainer::FLAG_BYPASS_SURFACE_CACHE;
+ }
+ if (aDest.Contains(aFill)) {
+ aImageFlags |= imgIContainer::FLAG_CLAMP;
+ }
+ int32_t appUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
+
+ SnappedImageDrawingParameters params = ComputeSnappedImageDrawingParameters(
+ &aContext, appUnitsPerDevPixel, aDest, aFill, aAnchor, aDirty, aImage,
+ aSamplingFilter, aImageFlags, aExtendMode);
+
+ if (!params.shouldDraw) {
+ return result;
+ }
+
+ {
+ gfxContextMatrixAutoSaveRestore contextMatrixRestorer(&aContext);
+
+ aContext.SetMatrixDouble(params.imageSpaceToDeviceSpace);
+
+ SVGImageContext newContext = aSVGContext;
+ if (!aSVGContext.GetViewportSize()) {
+ newContext.SetViewportSize(Some(params.svgViewportSize));
+ }
+
+ result = aImage->Draw(&aContext, params.size, params.region,
+ imgIContainer::FRAME_CURRENT, aSamplingFilter,
+ newContext, aImageFlags, aOpacity);
+ }
+
+ return result;
+}
+
+/* static */
+ImgDrawResult nsLayoutUtils::DrawSingleUnscaledImage(
+ gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter, const nsPoint& aDest,
+ const nsRect* aDirty, const SVGImageContext& aSVGContext,
+ uint32_t aImageFlags, const nsRect* aSourceArea) {
+ CSSIntSize imageSize;
+ aImage->GetWidth(&imageSize.width);
+ aImage->GetHeight(&imageSize.height);
+ aImage->GetResolution().ApplyTo(imageSize.width, imageSize.height);
+
+ if (imageSize.width < 1 || imageSize.height < 1) {
+ NS_WARNING("Image width or height is non-positive");
+ return ImgDrawResult::TEMPORARY_ERROR;
+ }
+
+ nsSize size(CSSPixel::ToAppUnits(imageSize));
+ nsRect source;
+ if (aSourceArea) {
+ source = *aSourceArea;
+ } else {
+ source.SizeTo(size);
+ }
+
+ nsRect dest(aDest - source.TopLeft(), size);
+ nsRect fill(aDest, source.Size());
+ // Ensure that only a single image tile is drawn. If aSourceArea extends
+ // outside the image bounds, we want to honor the aSourceArea-to-aDest
+ // translation but we don't want to actually tile the image.
+ fill.IntersectRect(fill, dest);
+ return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
+ dest, fill, aDest, aDirty ? *aDirty : dest,
+ aSVGContext, aImageFlags);
+}
+
+/* static */
+ImgDrawResult nsLayoutUtils::DrawSingleImage(
+ gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
+ SamplingFilter aSamplingFilter, const nsRect& aDest, const nsRect& aDirty,
+ const SVGImageContext& aSVGContext, uint32_t aImageFlags,
+ const nsPoint* aAnchorPoint) {
+ // NOTE(emilio): We can hardcode resolution to 1 here, since we're interested
+ // in the actual image pixels, for snapping purposes, not on the adjusted
+ // size.
+ CSSIntSize pixelImageSize(ComputeSizeForDrawingWithFallback(
+ aImage, ImageResolution(), aDest.Size()));
+ if (pixelImageSize.width < 1 || pixelImageSize.height < 1) {
+ NS_ASSERTION(pixelImageSize.width >= 0 && pixelImageSize.height >= 0,
+ "Image width or height is negative");
+ return ImgDrawResult::SUCCESS; // no point in drawing a zero size image
+ }
+
+ const nsSize imageSize(CSSPixel::ToAppUnits(pixelImageSize));
+ const nsRect source(nsPoint(), imageSize);
+ const nsRect dest = GetWholeImageDestination(imageSize, source, aDest);
+
+ // Ensure that only a single image tile is drawn. If aSourceArea extends
+ // outside the image bounds, we want to honor the aSourceArea-to-aDest
+ // transform but we don't want to actually tile the image.
+ nsRect fill;
+ fill.IntersectRect(aDest, dest);
+ return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
+ dest, fill,
+ aAnchorPoint ? *aAnchorPoint : fill.TopLeft(),
+ aDirty, aSVGContext, aImageFlags);
+}
+
+/* static */
+void nsLayoutUtils::ComputeSizeForDrawing(
+ imgIContainer* aImage, const ImageResolution& aResolution,
+ /* outparam */ CSSIntSize& aImageSize,
+ /* outparam */ AspectRatio& aIntrinsicRatio,
+ /* outparam */ bool& aGotWidth,
+ /* outparam */ bool& aGotHeight) {
+ aGotWidth = NS_SUCCEEDED(aImage->GetWidth(&aImageSize.width));
+ aGotHeight = NS_SUCCEEDED(aImage->GetHeight(&aImageSize.height));
+ Maybe<AspectRatio> intrinsicRatio = aImage->GetIntrinsicRatio();
+ aIntrinsicRatio = intrinsicRatio.valueOr(AspectRatio());
+
+ if (aGotWidth) {
+ aResolution.ApplyXTo(aImageSize.width);
+ }
+ if (aGotHeight) {
+ aResolution.ApplyYTo(aImageSize.height);
+ }
+
+ if (!(aGotWidth && aGotHeight) && intrinsicRatio.isNothing()) {
+ // We hit an error (say, because the image failed to load or couldn't be
+ // decoded) and should return zero size.
+ aGotWidth = aGotHeight = true;
+ aImageSize = CSSIntSize(0, 0);
+ }
+}
+
+/* static */
+CSSIntSize nsLayoutUtils::ComputeSizeForDrawingWithFallback(
+ imgIContainer* aImage, const ImageResolution& aResolution,
+ const nsSize& aFallbackSize) {
+ CSSIntSize imageSize;
+ AspectRatio imageRatio;
+ bool gotHeight, gotWidth;
+ ComputeSizeForDrawing(aImage, aResolution, imageSize, imageRatio, gotWidth,
+ gotHeight);
+
+ // If we didn't get both width and height, try to compute them using the
+ // intrinsic ratio of the image.
+ if (gotWidth != gotHeight) {
+ if (!gotWidth) {
+ if (imageRatio) {
+ imageSize.width = imageRatio.ApplyTo(imageSize.height);
+ gotWidth = true;
+ }
+ } else {
+ if (imageRatio) {
+ imageSize.height = imageRatio.Inverted().ApplyTo(imageSize.width);
+ gotHeight = true;
+ }
+ }
+ }
+
+ // If we still don't have a width or height, just use the fallback size the
+ // caller provided.
+ if (!gotWidth) {
+ imageSize.width =
+ nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.width);
+ }
+ if (!gotHeight) {
+ imageSize.height =
+ nsPresContext::AppUnitsToIntCSSPixels(aFallbackSize.height);
+ }
+
+ return imageSize;
+}
+
+/* static */ LayerIntRect SnapRectForImage(
+ const gfx::Matrix& aTransform, const gfx::MatrixScales& aScaleFactors,
+ const LayoutDeviceRect& aRect) {
+ // Attempt to snap pixels, the same as ComputeSnappedImageDrawingParameters.
+ // Any changes to the algorithm here will need to be reflected there.
+ bool snapped = false;
+ LayerIntRect snapRect;
+ if (!aTransform.HasNonAxisAlignedTransform() && aTransform._11 > 0.0 &&
+ aTransform._22 > 0.0) {
+ gfxRect rect(gfxPoint(aRect.X(), aRect.Y()),
+ gfxSize(aRect.Width(), aRect.Height()));
+
+ gfxPoint p1 =
+ ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopLeft())));
+ gfxPoint p2 =
+ ThebesPoint(aTransform.TransformPoint(ToPoint(rect.TopRight())));
+ gfxPoint p3 =
+ ThebesPoint(aTransform.TransformPoint(ToPoint(rect.BottomRight())));
+
+ if (p2 == gfxPoint(p1.x, p3.y) || p2 == gfxPoint(p3.x, p1.y)) {
+ p1.Round();
+ p3.Round();
+
+ IntPoint p1i(int32_t(p1.x), int32_t(p1.y));
+ IntPoint p3i(int32_t(p3.x), int32_t(p3.y));
+
+ snapRect.MoveTo(std::min(p1i.x, p3i.x), std::min(p1i.y, p3i.y));
+ snapRect.SizeTo(std::max(p1i.x, p3i.x) - snapRect.X(),
+ std::max(p1i.y, p3i.y) - snapRect.Y());
+ snapped = true;
+ }
+ }
+
+ if (!snapped) {
+ // If we couldn't snap directly with the transform, we need to go best
+ // effort in layer pixels.
+ snapRect = RoundedToInt(
+ aRect * LayoutDeviceToLayerScale2D::FromUnknownScale(aScaleFactors));
+ }
+
+ // An empty size is unacceptable so we ensure our suggested size is at least
+ // 1 pixel wide/tall.
+ if (snapRect.Width() < 1) {
+ snapRect.SetWidth(1);
+ }
+ if (snapRect.Height() < 1) {
+ snapRect.SetHeight(1);
+ }
+ return snapRect;
+}
+
+/* static */
+IntSize nsLayoutUtils::ComputeImageContainerDrawingParameters(
+ imgIContainer* aImage, nsIFrame* aForFrame,
+ const LayoutDeviceRect& aDestRect, const LayoutDeviceRect& aFillRect,
+ const StackingContextHelper& aSc, uint32_t aFlags,
+ SVGImageContext& aSVGContext, Maybe<ImageIntRegion>& aRegion) {
+ MOZ_ASSERT(aImage);
+ MOZ_ASSERT(aForFrame);
+
+ MatrixScales scaleFactors = aSc.GetInheritedScale();
+ SamplingFilter samplingFilter =
+ nsLayoutUtils::GetSamplingFilterForFrame(aForFrame);
+
+ // Compute our SVG context parameters, if any. Don't replace the viewport
+ // size if it was already set, prefer what the caller gave.
+ SVGImageContext::MaybeStoreContextPaint(aSVGContext, aForFrame, aImage);
+ if ((scaleFactors.xScale != 1.0 || scaleFactors.yScale != 1.0) &&
+ aImage->GetType() == imgIContainer::TYPE_VECTOR &&
+ (!aSVGContext.GetViewportSize())) {
+ gfxSize gfxDestSize(aDestRect.Width(), aDestRect.Height());
+ IntSize viewportSize = aImage->OptimalImageSizeForDest(
+ gfxDestSize, imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
+
+ CSSIntSize cssViewportSize(viewportSize.width, viewportSize.height);
+ aSVGContext.SetViewportSize(Some(cssViewportSize));
+ }
+
+ const gfx::Matrix& itm = aSc.GetInheritedTransform();
+ LayerIntRect destRect = SnapRectForImage(itm, scaleFactors, aDestRect);
+
+ // Since we always decode entire raster images, we only care about the
+ // ImageIntRegion for vector images when we are recording blobs, for which we
+ // may only draw part of in some cases.
+ if ((aImage->GetType() != imgIContainer::TYPE_VECTOR) ||
+ !(aFlags & imgIContainer::FLAG_RECORD_BLOB)) {
+ // If the transform scale of our stacking context helper is being animated
+ // on the compositor then the transform will have the current value of the
+ // scale, but the scale factors will have max value of the scale animation.
+ // So we want to ask for a decoded image that can fulfill that larger size.
+ int32_t scaleWidth = int32_t(ceil(aDestRect.Width() * scaleFactors.xScale));
+ if (scaleWidth > destRect.width + 2) {
+ destRect.width = scaleWidth;
+ }
+ int32_t scaleHeight =
+ int32_t(ceil(aDestRect.Height() * scaleFactors.yScale));
+ if (scaleHeight > destRect.height + 2) {
+ destRect.height = scaleHeight;
+ }
+
+ return aImage->OptimalImageSizeForDest(
+ gfxSize(destRect.Width(), destRect.Height()),
+ imgIContainer::FRAME_CURRENT, samplingFilter, aFlags);
+ }
+
+ // We only use the region rect with blob recordings. This is because when we
+ // rasterize an SVG image in process, we always create a complete
+ // rasterization of the whole image which can be given to any caller, while
+ // we support partial rasterization with the blob recordings.
+ if (aFlags & imgIContainer::FLAG_RECORD_BLOB) {
+ // If the dest rect contains the fill rect, then we are only displaying part
+ // of the vector image. We need to calculate the restriction region to avoid
+ // drawing more than we need, and sampling outside the desired bounds.
+ LayerIntRect clipRect = SnapRectForImage(itm, scaleFactors, aFillRect);
+ if (destRect.Contains(clipRect)) {
+ LayerIntRect restrictRect = destRect.Intersect(clipRect);
+ restrictRect.MoveBy(-destRect.TopLeft());
+
+ if (restrictRect.Width() < 1) {
+ restrictRect.SetWidth(1);
+ }
+ if (restrictRect.Height() < 1) {
+ restrictRect.SetHeight(1);
+ }
+
+ if (restrictRect.X() != 0 || restrictRect.Y() != 0 ||
+ restrictRect.Size() != destRect.Size()) {
+ IntRect sampleRect = restrictRect.ToUnknownRect();
+ aRegion = Some(ImageIntRegion::CreateWithSamplingRestriction(
+ sampleRect, sampleRect, ExtendMode::CLAMP));
+ }
+ }
+ }
+
+ // VectorImage::OptimalImageSizeForDest will just round up, but we already
+ // have an integer size.
+ return destRect.Size().ToUnknownSize();
+}
+
+/* static */
+nsPoint nsLayoutUtils::GetBackgroundFirstTilePos(const nsPoint& aDest,
+ const nsPoint& aFill,
+ const nsSize& aRepeatSize) {
+ return nsPoint(NSToIntFloor(float(aFill.x - aDest.x) / aRepeatSize.width) *
+ aRepeatSize.width,
+ NSToIntFloor(float(aFill.y - aDest.y) / aRepeatSize.height) *
+ aRepeatSize.height) +
+ aDest;
+}
+
+/* static */
+ImgDrawResult nsLayoutUtils::DrawBackgroundImage(
+ gfxContext& aContext, nsIFrame* aForFrame, nsPresContext* aPresContext,
+ imgIContainer* aImage, SamplingFilter aSamplingFilter, const nsRect& aDest,
+ const nsRect& aFill, const nsSize& aRepeatSize, const nsPoint& aAnchor,
+ const nsRect& aDirty, uint32_t aImageFlags, ExtendMode aExtendMode,
+ float aOpacity) {
+ AUTO_PROFILER_LABEL("nsLayoutUtils::DrawBackgroundImage",
+ GRAPHICS_Rasterization);
+
+ CSSIntSize destCSSSize{nsPresContext::AppUnitsToIntCSSPixels(aDest.width),
+ nsPresContext::AppUnitsToIntCSSPixels(aDest.height)};
+
+ SVGImageContext svgContext(Some(destCSSSize));
+ SVGImageContext::MaybeStoreContextPaint(svgContext, aForFrame, aImage);
+
+ /* Fast path when there is no need for image spacing */
+ if (aRepeatSize.width == aDest.width && aRepeatSize.height == aDest.height) {
+ return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
+ aDest, aFill, aAnchor, aDirty, svgContext,
+ aImageFlags, aExtendMode, aOpacity);
+ }
+
+ const nsPoint firstTilePos =
+ GetBackgroundFirstTilePos(aDest.TopLeft(), aFill.TopLeft(), aRepeatSize);
+ const nscoord xMost = aFill.XMost();
+ const nscoord repeatWidth = aRepeatSize.width;
+ const nscoord yMost = aFill.YMost();
+ const nscoord repeatHeight = aRepeatSize.height;
+ nsRect dest(0, 0, aDest.width, aDest.height);
+ nsPoint anchor = aAnchor;
+ for (nscoord x = firstTilePos.x; x < xMost; x += repeatWidth) {
+ for (nscoord y = firstTilePos.y; y < yMost; y += repeatHeight) {
+ dest.x = x;
+ dest.y = y;
+ ImgDrawResult result = DrawImageInternal(
+ aContext, aPresContext, aImage, aSamplingFilter, dest, dest, anchor,
+ aDirty, svgContext, aImageFlags, ExtendMode::CLAMP, aOpacity);
+ anchor.y += repeatHeight;
+ if (result != ImgDrawResult::SUCCESS) {
+ return result;
+ }
+ }
+ anchor.x += repeatWidth;
+ anchor.y = aAnchor.y;
+ }
+
+ return ImgDrawResult::SUCCESS;
+}
+
+/* static */
+ImgDrawResult nsLayoutUtils::DrawImage(
+ gfxContext& aContext, ComputedStyle* aComputedStyle,
+ nsPresContext* aPresContext, imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter, const nsRect& aDest,
+ const nsRect& aFill, const nsPoint& aAnchor, const nsRect& aDirty,
+ uint32_t aImageFlags, float aOpacity) {
+ SVGImageContext svgContext;
+ SVGImageContext::MaybeStoreContextPaint(svgContext, *aPresContext,
+ *aComputedStyle, aImage);
+
+ return DrawImageInternal(aContext, aPresContext, aImage, aSamplingFilter,
+ aDest, aFill, aAnchor, aDirty, svgContext,
+ aImageFlags, ExtendMode::CLAMP, aOpacity);
+}
+
+/* static */
+nsRect nsLayoutUtils::GetWholeImageDestination(const nsSize& aWholeImageSize,
+ const nsRect& aImageSourceArea,
+ const nsRect& aDestArea) {
+ double scaleX = double(aDestArea.width) / aImageSourceArea.width;
+ double scaleY = double(aDestArea.height) / aImageSourceArea.height;
+ nscoord destOffsetX = NSToCoordRound(aImageSourceArea.x * scaleX);
+ nscoord destOffsetY = NSToCoordRound(aImageSourceArea.y * scaleY);
+ nscoord wholeSizeX = NSToCoordRound(aWholeImageSize.width * scaleX);
+ nscoord wholeSizeY = NSToCoordRound(aWholeImageSize.height * scaleY);
+ return nsRect(aDestArea.TopLeft() - nsPoint(destOffsetX, destOffsetY),
+ nsSize(wholeSizeX, wholeSizeY));
+}
+
+/* static */
+already_AddRefed<imgIContainer> nsLayoutUtils::OrientImage(
+ imgIContainer* aContainer, const StyleImageOrientation& aOrientation) {
+ MOZ_ASSERT(aContainer, "Should have an image container");
+ nsCOMPtr<imgIContainer> img(aContainer);
+
+ switch (aOrientation) {
+ case StyleImageOrientation::FromImage:
+ break;
+ case StyleImageOrientation::None:
+ img = ImageOps::Unorient(img);
+ break;
+ }
+
+ return img.forget();
+}
+
+/* static */
+bool nsLayoutUtils::ImageRequestUsesCORS(imgIRequest* aRequest) {
+ int32_t corsMode = mozilla::CORS_NONE;
+ return NS_SUCCEEDED(aRequest->GetCORSMode(&corsMode)) &&
+ corsMode != mozilla::CORS_NONE;
+}
+
+static bool NonZeroCorner(const LengthPercentage& aLength) {
+ // Since negative results are clamped to 0, check > 0.
+ return aLength.Resolve(nscoord_MAX) > 0 || aLength.Resolve(0) > 0;
+}
+
+/* static */
+bool nsLayoutUtils::HasNonZeroCorner(const BorderRadius& aCorners) {
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ if (NonZeroCorner(aCorners.Get(corner))) return true;
+ }
+ return false;
+}
+
+// aCorner is a "full corner" value, i.e. eCornerTopLeft etc.
+static bool IsCornerAdjacentToSide(uint8_t aCorner, Side aSide) {
+ static_assert((int)eSideTop == eCornerTopLeft, "Check for Full Corner");
+ static_assert((int)eSideRight == eCornerTopRight, "Check for Full Corner");
+ static_assert((int)eSideBottom == eCornerBottomRight,
+ "Check for Full Corner");
+ static_assert((int)eSideLeft == eCornerBottomLeft, "Check for Full Corner");
+ static_assert((int)eSideTop == ((eCornerTopRight - 1) & 3),
+ "Check for Full Corner");
+ static_assert((int)eSideRight == ((eCornerBottomRight - 1) & 3),
+ "Check for Full Corner");
+ static_assert((int)eSideBottom == ((eCornerBottomLeft - 1) & 3),
+ "Check for Full Corner");
+ static_assert((int)eSideLeft == ((eCornerTopLeft - 1) & 3),
+ "Check for Full Corner");
+
+ return aSide == aCorner || aSide == ((aCorner - 1) & 3);
+}
+
+/* static */
+bool nsLayoutUtils::HasNonZeroCornerOnSide(const BorderRadius& aCorners,
+ Side aSide) {
+ static_assert(eCornerTopLeftX / 2 == eCornerTopLeft,
+ "Check for Non Zero on side");
+ static_assert(eCornerTopLeftY / 2 == eCornerTopLeft,
+ "Check for Non Zero on side");
+ static_assert(eCornerTopRightX / 2 == eCornerTopRight,
+ "Check for Non Zero on side");
+ static_assert(eCornerTopRightY / 2 == eCornerTopRight,
+ "Check for Non Zero on side");
+ static_assert(eCornerBottomRightX / 2 == eCornerBottomRight,
+ "Check for Non Zero on side");
+ static_assert(eCornerBottomRightY / 2 == eCornerBottomRight,
+ "Check for Non Zero on side");
+ static_assert(eCornerBottomLeftX / 2 == eCornerBottomLeft,
+ "Check for Non Zero on side");
+ static_assert(eCornerBottomLeftY / 2 == eCornerBottomLeft,
+ "Check for Non Zero on side");
+
+ for (const auto corner : mozilla::AllPhysicalHalfCorners()) {
+ // corner is a "half corner" value, so dividing by two gives us a
+ // "full corner" value.
+ if (NonZeroCorner(aCorners.Get(corner)) &&
+ IsCornerAdjacentToSide(corner / 2, aSide))
+ return true;
+ }
+ return false;
+}
+
+/* static */
+widget::TransparencyMode nsLayoutUtils::GetFrameTransparency(
+ nsIFrame* aBackgroundFrame, nsIFrame* aCSSRootFrame) {
+ if (!aCSSRootFrame->StyleEffects()->IsOpaque()) {
+ return TransparencyMode::Transparent;
+ }
+
+ if (HasNonZeroCorner(aCSSRootFrame->StyleBorder()->mBorderRadius)) {
+ return TransparencyMode::Transparent;
+ }
+
+ nsITheme::Transparency transparency;
+ if (aCSSRootFrame->IsThemed(&transparency)) {
+ return transparency == nsITheme::eTransparent
+ ? TransparencyMode::Transparent
+ : TransparencyMode::Opaque;
+ }
+
+ // We need an uninitialized window to be treated as opaque because doing
+ // otherwise breaks window display effects on some platforms, specifically
+ // Vista. (bug 450322)
+ if (aBackgroundFrame->IsViewportFrame() &&
+ !aBackgroundFrame->PrincipalChildList().FirstChild()) {
+ return TransparencyMode::Opaque;
+ }
+
+ const ComputedStyle* bgSC = nsCSSRendering::FindBackground(aBackgroundFrame);
+ if (!bgSC) {
+ return TransparencyMode::Transparent;
+ }
+ const nsStyleBackground* bg = bgSC->StyleBackground();
+ if (NS_GET_A(bg->BackgroundColor(bgSC)) < 255 ||
+ // bottom layer's clip is used for the color
+ bg->BottomLayer().mClip != StyleGeometryBox::BorderBox) {
+ return TransparencyMode::Transparent;
+ }
+ return TransparencyMode::Opaque;
+}
+
+/* static */
+bool nsLayoutUtils::IsPopup(const nsIFrame* aFrame) {
+ // Optimization: the frame can't possibly be a popup if it has no view.
+ if (!aFrame->HasView()) {
+ NS_ASSERTION(!aFrame->IsMenuPopupFrame(), "popup frame must have a view");
+ return false;
+ }
+ return aFrame->IsMenuPopupFrame();
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetDisplayRootFrame(nsIFrame* aFrame) {
+ return const_cast<nsIFrame*>(
+ nsLayoutUtils::GetDisplayRootFrame(const_cast<const nsIFrame*>(aFrame)));
+}
+
+/* static */
+const nsIFrame* nsLayoutUtils::GetDisplayRootFrame(const nsIFrame* aFrame) {
+ // We could use GetRootPresContext() here if the
+ // NS_FRAME_IN_POPUP frame bit is set.
+ const nsIFrame* f = aFrame;
+ for (;;) {
+ if (!f->HasAnyStateBits(NS_FRAME_IN_POPUP)) {
+ f = f->PresShell()->GetRootFrame();
+ if (!f) {
+ return aFrame;
+ }
+ } else if (IsPopup(f)) {
+ return f;
+ }
+ nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
+ if (!parent) return f;
+ f = parent;
+ }
+}
+
+/* static */
+nsIFrame* nsLayoutUtils::GetReferenceFrame(nsIFrame* aFrame) {
+ nsIFrame* f = aFrame;
+ for (;;) {
+ if (f->IsTransformed() || IsPopup(f)) {
+ return f;
+ }
+ nsIFrame* parent = GetCrossDocParentFrameInProcess(f);
+ if (!parent) {
+ return f;
+ }
+ f = parent;
+ }
+}
+
+/* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunFlagsForStyle(
+ const ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
+ const nsStyleFont* aStyleFont, const nsStyleText* aStyleText,
+ nscoord aLetterSpacing) {
+ gfx::ShapedTextFlags result = gfx::ShapedTextFlags();
+ if (aLetterSpacing != 0 ||
+ aStyleText->mTextJustify == StyleTextJustify::InterCharacter) {
+ result |= gfx::ShapedTextFlags::TEXT_DISABLE_OPTIONAL_LIGATURES;
+ }
+ if (aStyleText->mMozControlCharacterVisibility ==
+ StyleMozControlCharacterVisibility::Hidden) {
+ result |= gfx::ShapedTextFlags::TEXT_HIDE_CONTROL_CHARACTERS;
+ }
+ switch (aComputedStyle->StyleText()->mTextRendering) {
+ case StyleTextRendering::Optimizespeed:
+ result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
+ break;
+ case StyleTextRendering::Auto:
+ if (aPresContext &&
+ aStyleFont->mFont.size.ToCSSPixels() <
+ aPresContext->DevPixelsToFloatCSSPixels(
+ StaticPrefs::browser_display_auto_quality_min_font_size())) {
+ result |= gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED;
+ }
+ break;
+ default:
+ break;
+ }
+ return result | GetTextRunOrientFlagsForStyle(aComputedStyle);
+}
+
+/* static */ gfx::ShapedTextFlags nsLayoutUtils::GetTextRunOrientFlagsForStyle(
+ const ComputedStyle* aComputedStyle) {
+ auto writingMode = aComputedStyle->StyleVisibility()->mWritingMode;
+ switch (writingMode) {
+ case StyleWritingModeProperty::HorizontalTb:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_HORIZONTAL;
+
+ case StyleWritingModeProperty::VerticalLr:
+ case StyleWritingModeProperty::VerticalRl:
+ switch (aComputedStyle->StyleVisibility()->mTextOrientation) {
+ case StyleTextOrientation::Mixed:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED;
+ case StyleTextOrientation::Upright:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
+ case StyleTextOrientation::Sideways:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown text-orientation");
+ return gfx::ShapedTextFlags();
+ }
+
+ case StyleWritingModeProperty::SidewaysLr:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_LEFT;
+
+ case StyleWritingModeProperty::SidewaysRl:
+ return gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_SIDEWAYS_RIGHT;
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("unknown writing-mode");
+ return gfx::ShapedTextFlags();
+ }
+}
+
+/* static */
+void nsLayoutUtils::GetRectDifferenceStrips(const nsRect& aR1,
+ const nsRect& aR2, nsRect* aHStrip,
+ nsRect* aVStrip) {
+ NS_ASSERTION(aR1.TopLeft() == aR2.TopLeft(),
+ "expected rects at the same position");
+ nsRect unionRect(aR1.x, aR1.y, std::max(aR1.width, aR2.width),
+ std::max(aR1.height, aR2.height));
+ nscoord VStripStart = std::min(aR1.width, aR2.width);
+ nscoord HStripStart = std::min(aR1.height, aR2.height);
+ *aVStrip = unionRect;
+ aVStrip->x += VStripStart;
+ aVStrip->width -= VStripStart;
+ *aHStrip = unionRect;
+ aHStrip->y += HStripStart;
+ aHStrip->height -= HStripStart;
+}
+
+nsDeviceContext* nsLayoutUtils::GetDeviceContextForScreenInfo(
+ nsPIDOMWindowOuter* aWindow) {
+ if (!aWindow) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell();
+ while (docShell) {
+ // Now make sure our size is up to date. That will mean that the device
+ // context does the right thing on multi-monitor systems when we return it
+ // to the caller. It will also make sure that our prescontext has been
+ // created, if we're supposed to have one.
+ nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
+ if (!win) {
+ // No reason to go on
+ return nullptr;
+ }
+
+ win->EnsureSizeAndPositionUpToDate();
+
+ RefPtr<nsPresContext> presContext = docShell->GetPresContext();
+ if (presContext) {
+ nsDeviceContext* context = presContext->DeviceContext();
+ if (context) {
+ return context;
+ }
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> parentItem;
+ docShell->GetInProcessParent(getter_AddRefs(parentItem));
+ docShell = do_QueryInterface(parentItem);
+ }
+
+ return nullptr;
+}
+
+/* static */
+bool nsLayoutUtils::IsReallyFixedPos(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->StyleDisplay()->mPosition == StylePositionProperty::Fixed,
+ "IsReallyFixedPos called on non-'position:fixed' frame");
+ return MayBeReallyFixedPos(aFrame);
+}
+
+/* static */
+bool nsLayoutUtils::MayBeReallyFixedPos(const nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame->GetParent(),
+ "MayBeReallyFixedPos called on frame not in tree");
+ LayoutFrameType parentType = aFrame->GetParent()->Type();
+ return parentType == LayoutFrameType::Viewport ||
+ parentType == LayoutFrameType::PageContent;
+}
+
+/* static */
+bool nsLayoutUtils::IsInPositionFixedSubtree(const nsIFrame* aFrame) {
+ for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ if (f->StyleDisplay()->mPosition == StylePositionProperty::Fixed &&
+ nsLayoutUtils::IsReallyFixedPos(f)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static RefPtr<SourceSurface> ScaleSourceSurface(SourceSurface& aSurface,
+ const IntSize& aTargetSize) {
+ const IntSize surfaceSize = aSurface.GetSize();
+
+ MOZ_ASSERT(surfaceSize != aTargetSize);
+ MOZ_ASSERT(!surfaceSize.IsEmpty());
+ MOZ_ASSERT(!aTargetSize.IsEmpty());
+
+ RefPtr<DrawTarget> dt = Factory::CreateDrawTarget(
+ gfxVars::ContentBackend(), aTargetSize, aSurface.GetFormat());
+
+ if (!dt || !dt->IsValid()) {
+ return nullptr;
+ }
+
+ dt->DrawSurface(&aSurface, Rect(Point(), Size(aTargetSize)),
+ Rect(Point(), Size(surfaceSize)));
+ return dt->GetBackingSurface();
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromOffscreenCanvas(
+ OffscreenCanvas* aOffscreenCanvas, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ SurfaceFromElementResult result;
+
+ IntSize size = aOffscreenCanvas->GetWidthHeight();
+ if (size.IsEmpty()) {
+ return result;
+ }
+
+ result.mSourceSurface =
+ aOffscreenCanvas->GetSurfaceSnapshot(&result.mAlphaType);
+ if (!result.mSourceSurface) {
+ // If the element doesn't have a context then we won't get a snapshot. The
+ // canvas spec wants us to not error and just draw nothing, so return an
+ // empty surface.
+ result.mSize = size;
+ result.mAlphaType = gfxAlphaType::Opaque;
+ RefPtr<DrawTarget> ref =
+ aTarget ? aTarget : gfxPlatform::ThreadLocalScreenReferenceDrawTarget();
+ if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
+ RefPtr<DrawTarget> dt =
+ ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
+ if (dt) {
+ result.mSourceSurface = dt->Snapshot();
+ }
+ }
+ } else {
+ result.mSize = result.mSourceSurface->GetSize();
+
+ // If we want an exact sized surface, then we need to scale if we don't
+ // match the intrinsic size.
+ const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
+ if (exactSize && size != result.mSize) {
+ result.mSize = size;
+ result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
+ }
+
+ if (aTarget && result.mSourceSurface) {
+ RefPtr<SourceSurface> opt =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+ }
+
+ result.mHasSize = true;
+ result.mIntrinsicSize = size;
+ result.mIsWriteOnly = aOffscreenCanvas->IsWriteOnly();
+
+ nsIGlobalObject* global = aOffscreenCanvas->GetParentObject();
+ if (global) {
+ result.mPrincipal = global->PrincipalOrNull();
+ }
+
+ return result;
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromVideoFrame(
+ VideoFrame* aVideoFrame, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ SurfaceFromElementResult result;
+
+ RefPtr<layers::Image> layersImage = aVideoFrame->GetImage();
+ if (!layersImage) {
+ return result;
+ }
+
+ IntSize codedSize = aVideoFrame->NativeCodedSize();
+ IntRect visibleRect = aVideoFrame->NativeVisibleRect();
+ IntSize displaySize = aVideoFrame->NativeDisplaySize();
+
+ MOZ_ASSERT(layersImage->GetSize() == codedSize);
+ IntRect codedRect(IntPoint(0, 0), codedSize);
+
+ if (visibleRect.IsEqualEdges(codedRect) && displaySize == codedSize) {
+ // The display and coded rects are identical, which means we can just use
+ // the image as is.
+ result.mLayersImage = std::move(layersImage);
+ result.mSize = codedSize;
+ result.mIntrinsicSize = codedSize;
+ } else if (aSurfaceFlags & SFE_ALLOW_UNCROPPED_UNSCALED) {
+ // The caller supports cropping/scaling.
+ result.mLayersImage = std::move(layersImage);
+ result.mCropRect = Some(visibleRect);
+ result.mSize = codedSize;
+ result.mIntrinsicSize = displaySize;
+ } else {
+ // The caller does not support cropping/scaling. We need to on its behalf.
+ RefPtr<SourceSurface> surface = layersImage->GetAsSourceSurface();
+ if (!surface) {
+ return result;
+ }
+
+ RefPtr<DrawTarget> ref = aTarget
+ ? aTarget
+ : gfxPlatform::GetPlatform()
+ ->ThreadLocalScreenReferenceDrawTarget();
+ if (!ref->CanCreateSimilarDrawTarget(displaySize,
+ SurfaceFormat::B8G8R8A8)) {
+ return result;
+ }
+
+ RefPtr<DrawTarget> dt =
+ ref->CreateSimilarDrawTarget(displaySize, SurfaceFormat::B8G8R8A8);
+ if (!dt) {
+ return result;
+ }
+
+ gfx::Rect dstRect(0, 0, displaySize.Width(), displaySize.Height());
+ gfx::Rect srcRect(visibleRect.X(), visibleRect.Y(), visibleRect.Width(),
+ visibleRect.Height());
+ dt->DrawSurface(surface, dstRect, srcRect);
+ result.mSourceSurface = dt->Snapshot();
+ if (NS_WARN_IF(!result.mSourceSurface)) {
+ return result;
+ }
+
+ result.mSize = displaySize;
+ result.mIntrinsicSize = displaySize;
+ }
+
+ result.mAlphaType = gfxAlphaType::Premult;
+ Nullable<VideoPixelFormat> format = aVideoFrame->GetFormat();
+ if (!format.IsNull()) {
+ switch (format.Value()) {
+ case VideoPixelFormat::I420:
+ case VideoPixelFormat::I422:
+ case VideoPixelFormat::I444:
+ case VideoPixelFormat::NV12:
+ case VideoPixelFormat::RGBX:
+ case VideoPixelFormat::BGRX:
+ result.mAlphaType = gfxAlphaType::Opaque;
+ break;
+ default:
+ break;
+ }
+ }
+
+ result.mHasSize = true;
+
+ // We shouldn't have a VideoFrame if either of these is true.
+ result.mHadCrossOriginRedirects = false;
+ result.mIsWriteOnly = false;
+
+ nsIGlobalObject* global = aVideoFrame->GetParentObject();
+ if (global) {
+ result.mPrincipal = global->PrincipalOrNull();
+ }
+
+ if (aTarget) {
+ // They gave us a DrawTarget to optimize for, so even though we may have a
+ // layers::Image, we should unconditionally try to grab a SourceSurface and
+ // try to optimize it.
+ if (result.mLayersImage) {
+ MOZ_ASSERT(!result.mSourceSurface);
+ result.mSourceSurface = result.mLayersImage->GetAsSourceSurface();
+ }
+
+ if (result.mSourceSurface) {
+ RefPtr<SourceSurface> opt =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = std::move(opt);
+ }
+ }
+ }
+
+ return result;
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromImageBitmap(
+ mozilla::dom::ImageBitmap* aImageBitmap, uint32_t aSurfaceFlags) {
+ return aImageBitmap->SurfaceFrom(aSurfaceFlags);
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
+ nsIImageLoadingContent* aElement, const Maybe<int32_t>& aResizeWidth,
+ const Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ SurfaceFromElementResult result;
+ nsresult rv;
+
+ nsCOMPtr<imgIRequest> imgRequest;
+ rv = aElement->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgRequest));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ if (!imgRequest) {
+ // There's no image request. This is either because a request for
+ // a non-empty URI failed, or the URI is the empty string.
+ nsCOMPtr<nsIURI> currentURI;
+ aElement->GetCurrentURI(getter_AddRefs(currentURI));
+ if (!currentURI) {
+ // Treat the empty URI as available instead of broken state.
+ result.mHasSize = true;
+ }
+ return result;
+ }
+
+ uint32_t status;
+ imgRequest->GetImageStatus(&status);
+ result.mHasSize = status & imgIRequest::STATUS_SIZE_AVAILABLE;
+ if ((status & imgIRequest::STATUS_LOAD_COMPLETE) == 0) {
+ // Spec says to use GetComplete, but that only works on
+ // HTMLImageElement, and we support all sorts of other stuff
+ // here. Do this for now pending spec clarification.
+ result.mIsStillLoading = (status & imgIRequest::STATUS_ERROR) == 0;
+ return result;
+ }
+
+ nsCOMPtr<nsIPrincipal> principal;
+ rv = imgRequest->GetImagePrincipal(getter_AddRefs(principal));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ nsCOMPtr<imgIContainer> imgContainer;
+ rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
+ if (NS_FAILED(rv)) {
+ return result;
+ }
+
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aElement);
+
+ // Ensure that the image is oriented the same way as it's displayed
+ // if the image request is of the same origin.
+ auto orientation =
+ content->GetPrimaryFrame() &&
+ !(aSurfaceFlags & SFE_ORIENTATION_FROM_IMAGE)
+ ? content->GetPrimaryFrame()->StyleVisibility()->UsedImageOrientation(
+ imgRequest)
+ : nsStyleVisibility::UsedImageOrientation(
+ imgRequest, StyleImageOrientation::FromImage);
+ imgContainer = OrientImage(imgContainer, orientation);
+
+ const bool noRasterize = aSurfaceFlags & SFE_NO_RASTERIZING_VECTORS;
+
+ uint32_t whichFrame = aSurfaceFlags & SFE_WANT_FIRST_FRAME_IF_IMAGE
+ ? (uint32_t)imgIContainer::FRAME_FIRST
+ : (uint32_t)imgIContainer::FRAME_CURRENT;
+ const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
+
+ uint32_t frameFlags =
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (aSurfaceFlags & SFE_NO_COLORSPACE_CONVERSION)
+ frameFlags |= imgIContainer::FLAG_DECODE_NO_COLORSPACE_CONVERSION;
+ if (aSurfaceFlags & SFE_ALLOW_NON_PREMULT) {
+ frameFlags |= imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA;
+ }
+
+ int32_t imgWidth, imgHeight;
+ HTMLImageElement* element = HTMLImageElement::FromNodeOrNull(content);
+ if (aSurfaceFlags & SFE_USE_ELEMENT_SIZE_IF_VECTOR && element &&
+ imgContainer->GetType() == imgIContainer::TYPE_VECTOR) {
+ // We're holding a strong ref to "element" via "content".
+ imgWidth = MOZ_KnownLive(element)->Width();
+ imgHeight = MOZ_KnownLive(element)->Height();
+ } else {
+ auto res = imgContainer->GetResolution();
+ rv = imgContainer->GetWidth(&imgWidth);
+ if (NS_SUCCEEDED(rv)) {
+ res.ApplyXTo(imgWidth);
+ } else if (aResizeWidth.isSome()) {
+ imgWidth = *aResizeWidth;
+ } else {
+ // As stated in css-sizing-3 Intrinsic Sizes, the fallback size of
+ // 300 x 150 for the width and height as needed.
+ //
+ // See https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
+ imgWidth = kFallbackIntrinsicWidthInPixels;
+ }
+ rv = imgContainer->GetHeight(&imgHeight);
+ if (NS_SUCCEEDED(rv)) {
+ res.ApplyYTo(imgHeight);
+ } else if (aResizeHeight.isSome()) {
+ imgHeight = *aResizeHeight;
+ } else {
+ // As stated in css-sizing-3 Intrinsic Sizes, the fallback size of
+ // 300 x 150 for the width and height as needed.
+ //
+ // See https://drafts.csswg.org/css-sizing-3/#intrinsic-sizes
+ imgHeight = kFallbackIntrinsicHeightInPixels;
+ }
+ }
+ result.mSize = result.mIntrinsicSize = IntSize(imgWidth, imgHeight);
+
+ if (!noRasterize || imgContainer->GetType() == imgIContainer::TYPE_RASTER) {
+ result.mSourceSurface =
+ imgContainer->GetFrameAtSize(result.mSize, whichFrame, frameFlags);
+ if (!result.mSourceSurface) {
+ return result;
+ }
+ IntSize surfSize = result.mSourceSurface->GetSize();
+ if (exactSize && surfSize != result.mSize) {
+ result.mSourceSurface =
+ ScaleSourceSurface(*result.mSourceSurface, result.mSize);
+ if (!result.mSourceSurface) {
+ return result;
+ }
+ } else {
+ result.mSize = surfSize;
+ }
+ // The surface we return is likely to be cached. We don't want to have to
+ // convert to a surface that's compatible with aTarget each time it's used
+ // (that would result in terrible performance), so we convert once here
+ // upfront if aTarget is specified.
+ if (aTarget) {
+ RefPtr<SourceSurface> optSurface =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (optSurface) {
+ result.mSourceSurface = optSurface;
+ }
+ }
+
+ const auto& format = result.mSourceSurface->GetFormat();
+ if (IsOpaque(format)) {
+ result.mAlphaType = gfxAlphaType::Opaque;
+ } else if (frameFlags & imgIContainer::FLAG_DECODE_NO_PREMULTIPLY_ALPHA) {
+ result.mAlphaType = gfxAlphaType::NonPremult;
+ } else {
+ result.mAlphaType = gfxAlphaType::Premult;
+ }
+ } else {
+ result.mDrawInfo.mImgContainer = imgContainer;
+ result.mDrawInfo.mWhichFrame = whichFrame;
+ result.mDrawInfo.mDrawingFlags = frameFlags;
+ }
+
+ result.mCORSUsed = nsLayoutUtils::ImageRequestUsesCORS(imgRequest);
+
+ bool hadCrossOriginRedirects = true;
+ imgRequest->GetHadCrossOriginRedirects(&hadCrossOriginRedirects);
+
+ result.mPrincipal = std::move(principal);
+ result.mHadCrossOriginRedirects = hadCrossOriginRedirects;
+ result.mImageRequest = std::move(imgRequest);
+ result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
+ result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
+
+ return result;
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
+ HTMLImageElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ return SurfaceFromElement(static_cast<nsIImageLoadingContent*>(aElement),
+ Nothing(), Nothing(), aSurfaceFlags, aTarget);
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
+ HTMLCanvasElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ SurfaceFromElementResult result;
+
+ IntSize size = aElement->GetSize();
+ if (size.IsEmpty()) {
+ return result;
+ }
+
+ auto pAlphaType = &result.mAlphaType;
+ if (!(aSurfaceFlags & SFE_ALLOW_NON_PREMULT)) {
+ pAlphaType =
+ nullptr; // Coersce GetSurfaceSnapshot to give us Opaque/Premult only.
+ }
+ result.mSourceSurface = aElement->GetSurfaceSnapshot(pAlphaType, aTarget);
+ if (!result.mSourceSurface) {
+ // If the element doesn't have a context then we won't get a snapshot. The
+ // canvas spec wants us to not error and just draw nothing, so return an
+ // empty surface.
+ result.mSize = size;
+ result.mAlphaType = gfxAlphaType::Opaque;
+ RefPtr<DrawTarget> ref =
+ aTarget ? aTarget
+ : gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
+ if (ref->CanCreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8)) {
+ RefPtr<DrawTarget> dt =
+ ref->CreateSimilarDrawTarget(size, SurfaceFormat::B8G8R8A8);
+ if (dt) {
+ result.mSourceSurface = dt->Snapshot();
+ }
+ }
+ } else {
+ result.mSize = result.mSourceSurface->GetSize();
+
+ // If we want an exact sized surface, then we need to scale if we don't
+ // match the intrinsic size.
+ const bool exactSize = aSurfaceFlags & SFE_EXACT_SIZE_SURFACE;
+ if (exactSize && size != result.mSize) {
+ result.mSize = size;
+ result.mSourceSurface = ScaleSourceSurface(*result.mSourceSurface, size);
+ }
+
+ if (aTarget && result.mSourceSurface) {
+ RefPtr<SourceSurface> opt =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+ }
+
+ // Ensure that any future changes to the canvas trigger proper invalidation,
+ // in case this is being used by -moz-element()
+ aElement->MarkContextClean();
+
+ result.mHasSize = true;
+ result.mIntrinsicSize = size;
+ result.mPrincipal = aElement->NodePrincipal();
+ result.mHadCrossOriginRedirects = false;
+ result.mIsWriteOnly = aElement->IsWriteOnly();
+
+ return result;
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
+ HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ SurfaceFromElementResult result;
+ result.mAlphaType = gfxAlphaType::Opaque; // Assume opaque.
+
+ if (aElement->ContainsRestrictedContent()) {
+ return result;
+ }
+
+ uint16_t readyState = aElement->ReadyState();
+ if (readyState == HAVE_NOTHING || readyState == HAVE_METADATA) {
+ result.mIsStillLoading = true;
+ return result;
+ }
+
+ // If it doesn't have a principal, just bail
+ nsCOMPtr<nsIPrincipal> principal = aElement->GetCurrentVideoPrincipal();
+ if (!principal) {
+ return result;
+ }
+
+ result.mLayersImage = aElement->GetCurrentImage();
+ if (!result.mLayersImage) {
+ return result;
+ }
+
+ result.mCORSUsed = aElement->GetCORSMode() != CORS_NONE;
+ result.mHasSize = true;
+ result.mSize = result.mLayersImage->GetSize();
+ result.mIntrinsicSize =
+ gfx::IntSize(aElement->VideoWidth(), aElement->VideoHeight());
+ result.mPrincipal = std::move(principal);
+ result.mHadCrossOriginRedirects = aElement->HadCrossOriginRedirects();
+ result.mIsWriteOnly = CanvasUtils::CheckWriteOnlySecurity(
+ result.mCORSUsed, result.mPrincipal, result.mHadCrossOriginRedirects);
+
+ if (aTarget) {
+ // They gave us a DrawTarget to optimize for, so even though we have a
+ // layers::Image, we should unconditionally try to grab a SourceSurface and
+ // try to optimize it.
+ if ((result.mSourceSurface = result.mLayersImage->GetAsSourceSurface())) {
+ RefPtr<SourceSurface> opt =
+ aTarget->OptimizeSourceSurface(result.mSourceSurface);
+ if (opt) {
+ result.mSourceSurface = opt;
+ }
+ }
+ }
+
+ return result;
+}
+
+SurfaceFromElementResult nsLayoutUtils::SurfaceFromElement(
+ dom::Element* aElement, const Maybe<int32_t>& aResizeWidth,
+ const Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ // If it's a <canvas>, we may be able to just grab its internal surface
+ if (HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(aElement)) {
+ return SurfaceFromElement(canvas, aSurfaceFlags, aTarget);
+ }
+
+ // Maybe it's <video>?
+ if (HTMLVideoElement* video = HTMLVideoElement::FromNodeOrNull(aElement)) {
+ return SurfaceFromElement(video, aSurfaceFlags, aTarget);
+ }
+
+ // Finally, check if it's a normal image
+ nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(aElement);
+
+ if (!imageLoader) {
+ return SurfaceFromElementResult();
+ }
+
+ return SurfaceFromElement(imageLoader, aResizeWidth, aResizeHeight,
+ aSurfaceFlags, aTarget);
+}
+
+/* static */
+Element* nsLayoutUtils::GetEditableRootContentByContentEditable(
+ Document* aDocument) {
+ // If the document is in designMode we should return nullptr.
+ if (!aDocument || aDocument->IsInDesignMode()) {
+ return nullptr;
+ }
+
+ // contenteditable only works with HTML document.
+ // XXXbz should this test IsHTMLOrXHTML(), or just IsHTML()?
+ if (!aDocument->IsHTMLOrXHTML()) {
+ return nullptr;
+ }
+
+ Element* rootElement = aDocument->GetRootElement();
+ if (rootElement && rootElement->IsEditable()) {
+ return rootElement;
+ }
+
+ // If there is no editable root element, check its <body> element.
+ // Note that the body element could be <frameset> element.
+ Element* bodyElement = aDocument->GetBody();
+ if (bodyElement && bodyElement->IsEditable()) {
+ return bodyElement;
+ }
+ return nullptr;
+}
+
+#ifdef DEBUG
+/* static */
+void nsLayoutUtils::AssertNoDuplicateContinuations(
+ nsIFrame* aContainer, const nsFrameList& aFrameList) {
+ for (nsIFrame* f : aFrameList) {
+ // Check only later continuations of f; we deal with checking the
+ // earlier continuations when we hit those earlier continuations in
+ // the frame list.
+ for (nsIFrame* c = f; (c = c->GetNextInFlow());) {
+ NS_ASSERTION(c->GetParent() != aContainer || !aFrameList.ContainsFrame(c),
+ "Two continuations of the same frame in the same "
+ "frame list");
+ }
+ }
+}
+
+// Is one of aFrame's ancestors a letter frame?
+static bool IsInLetterFrame(nsIFrame* aFrame) {
+ for (nsIFrame* f = aFrame->GetParent(); f; f = f->GetParent()) {
+ if (f->IsLetterFrame()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+void nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(nsIFrame* aSubtreeRoot) {
+ NS_ASSERTION(aSubtreeRoot->GetPrevInFlow(),
+ "frame tree not empty, but caller reported complete status");
+
+ // Also assert that text frames map no text.
+ auto [start, end] = aSubtreeRoot->GetOffsets();
+ // In some cases involving :first-letter, we'll partially unlink a
+ // continuation in the middle of a continuation chain from its
+ // previous and next continuations before destroying it, presumably so
+ // that we don't also destroy the later continuations. Once we've
+ // done this, GetOffsets returns incorrect values.
+ // For examples, see list of tests in
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=619021#c29
+ NS_ASSERTION(start == end || IsInLetterFrame(aSubtreeRoot),
+ "frame tree not empty, but caller reported complete status");
+
+ for (const auto& childList : aSubtreeRoot->ChildLists()) {
+ for (nsIFrame* child : childList.mList) {
+ nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(child);
+ }
+ }
+}
+#endif
+
+static void GetFontFacesForFramesInner(
+ nsIFrame* aFrame, nsLayoutUtils::UsedFontFaceList& aResult,
+ nsLayoutUtils::UsedFontFaceTable& aFontFaces, uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace) {
+ MOZ_ASSERT(aFrame, "NULL frame pointer");
+
+ if (aFrame->IsTextFrame()) {
+ if (!aFrame->GetPrevContinuation()) {
+ nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true, aResult,
+ aFontFaces, aMaxRanges,
+ aSkipCollapsedWhitespace);
+ }
+ return;
+ }
+
+ FrameChildListID childLists[] = {FrameChildListID::Principal,
+ FrameChildListID::Popup};
+ for (size_t i = 0; i < ArrayLength(childLists); ++i) {
+ for (nsIFrame* child : aFrame->GetChildList(childLists[i])) {
+ child = nsPlaceholderFrame::GetRealFrameFor(child);
+ GetFontFacesForFramesInner(child, aResult, aFontFaces, aMaxRanges,
+ aSkipCollapsedWhitespace);
+ }
+ }
+}
+
+/* static */
+nsresult nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
+ UsedFontFaceList& aResult,
+ UsedFontFaceTable& aFontFaces,
+ uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace) {
+ MOZ_ASSERT(aFrame, "NULL frame pointer");
+
+ while (aFrame) {
+ GetFontFacesForFramesInner(aFrame, aResult, aFontFaces, aMaxRanges,
+ aSkipCollapsedWhitespace);
+ aFrame = GetNextContinuationOrIBSplitSibling(aFrame);
+ }
+
+ return NS_OK;
+}
+
+static void AddFontsFromTextRun(gfxTextRun* aTextRun, nsTextFrame* aFrame,
+ gfxSkipCharsIterator& aSkipIter,
+ const gfxTextRun::Range& aRange,
+ nsLayoutUtils::UsedFontFaceList& aResult,
+ nsLayoutUtils::UsedFontFaceTable& aFontFaces,
+ uint32_t aMaxRanges) {
+ nsIContent* content = aFrame->GetContent();
+ int32_t contentLimit =
+ aFrame->GetContentOffset() + aFrame->GetInFlowContentLength();
+ for (gfxTextRun::GlyphRunIterator glyphRuns(aTextRun, aRange);
+ !glyphRuns.AtEnd(); glyphRuns.NextRun()) {
+ gfxFontEntry* fe = glyphRuns.GlyphRun()->mFont->GetFontEntry();
+ // if we have already listed this face, just make sure the match type is
+ // recorded
+ InspectorFontFace* fontFace = aFontFaces.Get(fe);
+ if (fontFace) {
+ fontFace->AddMatchType(glyphRuns.GlyphRun()->mMatchType);
+ } else {
+ // A new font entry we haven't seen before
+ fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(),
+ glyphRuns.GlyphRun()->mMatchType);
+ aFontFaces.InsertOrUpdate(fe, fontFace);
+ aResult.AppendElement(fontFace);
+ }
+
+ // Add this glyph run to the fontFace's list of ranges, unless we have
+ // already collected as many as wanted.
+ if (fontFace->RangeCount() < aMaxRanges) {
+ int32_t start =
+ aSkipIter.ConvertSkippedToOriginal(glyphRuns.StringStart());
+ int32_t end = aSkipIter.ConvertSkippedToOriginal(glyphRuns.StringEnd());
+
+ // Mapping back from textrun offsets ("skipped" offsets that reflect the
+ // text after whitespace collapsing, etc) to DOM content offsets in the
+ // original text is ambiguous, because many original characters can
+ // map to a single skipped offset. aSkipIter.ConvertSkippedToOriginal()
+ // will return an "original" offset that corresponds to the *end* of
+ // a collapsed run of characters in this case; but that might extend
+ // beyond the current content node if the textrun mapped multiple nodes.
+ // So we clamp the end offset to keep it valid for the content node
+ // that corresponds to the current textframe.
+ end = std::min(end, contentLimit);
+
+ if (end > start) {
+ RefPtr<nsRange> range =
+ nsRange::Create(content, start, content, end, IgnoreErrors());
+ NS_WARNING_ASSERTION(range,
+ "nsRange::Create() failed to create valid range");
+ if (range) {
+ fontFace->AddRange(range);
+ }
+ }
+ }
+ }
+}
+
+/* static */
+void nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame, int32_t aStartOffset,
+ int32_t aEndOffset,
+ bool aFollowContinuations,
+ UsedFontFaceList& aResult,
+ UsedFontFaceTable& aFontFaces,
+ uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace) {
+ MOZ_ASSERT(aFrame, "NULL frame pointer");
+
+ if (!aFrame->IsTextFrame()) {
+ return;
+ }
+
+ if (!aFrame->StyleVisibility()->IsVisible()) {
+ return;
+ }
+
+ nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
+ do {
+ int32_t fstart = std::max(curr->GetContentOffset(), aStartOffset);
+ int32_t fend = std::min(curr->GetContentEnd(), aEndOffset);
+ if (fstart >= fend) {
+ curr = static_cast<nsTextFrame*>(curr->GetNextContinuation());
+ continue;
+ }
+
+ // curr is overlapping with the offset we want
+ gfxSkipCharsIterator iter = curr->EnsureTextRun(nsTextFrame::eInflated);
+ gfxTextRun* textRun = curr->GetTextRun(nsTextFrame::eInflated);
+ if (!textRun) {
+ NS_WARNING("failed to get textRun, low memory?");
+ return;
+ }
+
+ // include continuations in the range that share the same textrun
+ nsTextFrame* next = nullptr;
+ if (aFollowContinuations && fend < aEndOffset) {
+ next = static_cast<nsTextFrame*>(curr->GetNextContinuation());
+ while (next && next->GetTextRun(nsTextFrame::eInflated) == textRun) {
+ fend = std::min(next->GetContentEnd(), aEndOffset);
+ next = fend < aEndOffset
+ ? static_cast<nsTextFrame*>(next->GetNextContinuation())
+ : nullptr;
+ }
+ }
+
+ if (!aSkipCollapsedWhitespace || (curr->HasAnyNoncollapsedCharacters() &&
+ curr->HasNonSuppressedText())) {
+ gfxTextRun::Range range(iter.ConvertOriginalToSkipped(fstart),
+ iter.ConvertOriginalToSkipped(fend));
+ AddFontsFromTextRun(textRun, curr, iter, range, aResult, aFontFaces,
+ aMaxRanges);
+ }
+
+ curr = next;
+ } while (aFollowContinuations && curr);
+}
+
+/* static */
+size_t nsLayoutUtils::SizeOfTextRunsForFrames(nsIFrame* aFrame,
+ MallocSizeOf aMallocSizeOf,
+ bool clear) {
+ MOZ_ASSERT(aFrame, "NULL frame pointer");
+
+ size_t total = 0;
+
+ if (aFrame->IsTextFrame()) {
+ nsTextFrame* textFrame = static_cast<nsTextFrame*>(aFrame);
+ for (uint32_t i = 0; i < 2; ++i) {
+ gfxTextRun* run = textFrame->GetTextRun(
+ (i != 0) ? nsTextFrame::eInflated : nsTextFrame::eNotInflated);
+ if (run) {
+ if (clear) {
+ run->ResetSizeOfAccountingFlags();
+ } else {
+ total += run->MaybeSizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ }
+ return total;
+ }
+
+ for (const auto& childList : aFrame->ChildLists()) {
+ for (nsIFrame* f : childList.mList) {
+ total += SizeOfTextRunsForFrames(f, aMallocSizeOf, clear);
+ }
+ }
+ return total;
+}
+
+/* static */
+void nsLayoutUtils::RecomputeSmoothScrollDefault() {
+ if (nsContentUtils::ShouldResistFingerprinting(
+ "We use the global RFP pref to maintain consistent scroll behavior "
+ "in the browser.",
+ RFPTarget::CSSPrefersReducedMotion)) {
+ // When resist fingerprinting is enabled, we should not default disable
+ // smooth scrolls when the user prefers-reduced-motion to avoid leaking
+ // the value of the OS pref to sites.
+ Preferences::SetBool(StaticPrefs::GetPrefName_general_smoothScroll(), true,
+ PrefValueKind::Default);
+ } else {
+ // We want prefers-reduced-motion to determine the default
+ // value of the general.smoothScroll pref. If the user
+ // changed the pref we want to respect the change.
+ Preferences::SetBool(
+ StaticPrefs::GetPrefName_general_smoothScroll(),
+ !LookAndFeel::GetInt(LookAndFeel::IntID::PrefersReducedMotion, 0),
+ PrefValueKind::Default);
+ }
+}
+
+/* static */
+bool nsLayoutUtils::IsSmoothScrollingEnabled() {
+ return StaticPrefs::general_smoothScroll();
+}
+
+/* static */
+void nsLayoutUtils::Initialize() {
+ nsComputedDOMStyle::RegisterPrefChangeCallbacks();
+}
+
+/* static */
+void nsLayoutUtils::Shutdown() {
+ if (sContentMap) {
+ sContentMap = nullptr;
+ }
+
+ nsComputedDOMStyle::UnregisterPrefChangeCallbacks();
+}
+
+/* static */
+void nsLayoutUtils::RegisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered) {
+ if (!aPresContext) {
+ return;
+ }
+
+ if (aRequestRegistered && *aRequestRegistered) {
+ // Our request is already registered with the refresh driver, so
+ // no need to register it again.
+ return;
+ }
+
+ if (aRequest) {
+ aPresContext->RefreshDriver()->AddImageRequest(aRequest);
+ if (aRequestRegistered) {
+ *aRequestRegistered = true;
+ }
+ }
+}
+
+/* static */
+void nsLayoutUtils::RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered) {
+ if (!aPresContext) {
+ return;
+ }
+
+ if (aRequestRegistered && *aRequestRegistered) {
+ // Our request is already registered with the refresh driver, so
+ // no need to register it again.
+ return;
+ }
+
+ if (aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ // Check to verify that the image is animated. If so, then add it to the
+ // list of images tracked by the refresh driver.
+ bool isAnimated = false;
+ nsresult rv = image->GetAnimated(&isAnimated);
+ if (NS_SUCCEEDED(rv) && isAnimated) {
+ aPresContext->RefreshDriver()->AddImageRequest(aRequest);
+ if (aRequestRegistered) {
+ *aRequestRegistered = true;
+ }
+ }
+ }
+ }
+}
+
+/* static */
+void nsLayoutUtils::DeregisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered) {
+ if (!aPresContext) {
+ return;
+ }
+
+ // Deregister our imgIRequest with the refresh driver to
+ // complete tear-down, but only if it has been registered
+ if (aRequestRegistered && !*aRequestRegistered) {
+ return;
+ }
+
+ if (aRequest) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(aRequest->GetImage(getter_AddRefs(image)))) {
+ aPresContext->RefreshDriver()->RemoveImageRequest(aRequest);
+
+ if (aRequestRegistered) {
+ *aRequestRegistered = false;
+ }
+ }
+ }
+}
+
+/* static */
+void nsLayoutUtils::PostRestyleEvent(Element* aElement,
+ RestyleHint aRestyleHint,
+ nsChangeHint aMinChangeHint) {
+ if (Document* doc = aElement->GetComposedDoc()) {
+ if (nsPresContext* presContext = doc->GetPresContext()) {
+ presContext->RestyleManager()->PostRestyleEvent(aElement, aRestyleHint,
+ aMinChangeHint);
+ }
+ }
+}
+
+nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
+ const nsAString& aValue)
+ : mozilla::Runnable("nsSetAttrRunnable"),
+ mElement(aElement),
+ mAttrName(aAttrName),
+ mValue(aValue) {
+ NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
+}
+
+nsSetAttrRunnable::nsSetAttrRunnable(Element* aElement, nsAtom* aAttrName,
+ int32_t aValue)
+ : mozilla::Runnable("nsSetAttrRunnable"),
+ mElement(aElement),
+ mAttrName(aAttrName) {
+ NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
+ mValue.AppendInt(aValue);
+}
+
+NS_IMETHODIMP
+nsSetAttrRunnable::Run() {
+ return mElement->SetAttr(kNameSpaceID_None, mAttrName, mValue, true);
+}
+
+nsUnsetAttrRunnable::nsUnsetAttrRunnable(Element* aElement, nsAtom* aAttrName)
+ : mozilla::Runnable("nsUnsetAttrRunnable"),
+ mElement(aElement),
+ mAttrName(aAttrName) {
+ NS_ASSERTION(aElement && aAttrName, "Missing stuff, prepare to crash");
+}
+
+NS_IMETHODIMP
+nsUnsetAttrRunnable::Run() {
+ return mElement->UnsetAttr(kNameSpaceID_None, mAttrName, true);
+}
+
+/**
+ * Compute the minimum font size inside of a container with the given
+ * width, such that **when the user zooms the container to fill the full
+ * width of the device**, the fonts satisfy our minima.
+ */
+static nscoord MinimumFontSizeFor(nsPresContext* aPresContext,
+ WritingMode aWritingMode,
+ nscoord aContainerISize) {
+ PresShell* presShell = aPresContext->PresShell();
+
+ uint32_t emPerLine = presShell->FontSizeInflationEmPerLine();
+ uint32_t minTwips = presShell->FontSizeInflationMinTwips();
+ if (emPerLine == 0 && minTwips == 0) {
+ return 0;
+ }
+
+ nscoord byLine = 0, byInch = 0;
+ if (emPerLine != 0) {
+ byLine = aContainerISize / emPerLine;
+ }
+ if (minTwips != 0) {
+ // REVIEW: Is this giving us app units and sizes *not* counting
+ // viewport scaling?
+ gfxSize screenSize = aPresContext->ScreenSizeInchesForFontInflation();
+ float deviceISizeInches =
+ aWritingMode.IsVertical() ? screenSize.height : screenSize.width;
+ byInch =
+ NSToCoordRound(aContainerISize / (deviceISizeInches * 1440 / minTwips));
+ }
+ return std::max(byLine, byInch);
+}
+
+/* static */
+float nsLayoutUtils::FontSizeInflationInner(const nsIFrame* aFrame,
+ nscoord aMinFontSize) {
+ // Note that line heights should be inflated by the same ratio as the
+ // font size of the same text; thus we operate only on the font size
+ // even when we're scaling a line height.
+ nscoord styleFontSize = aFrame->StyleFont()->mFont.size.ToAppUnits();
+ if (styleFontSize <= 0) {
+ // Never scale zero font size.
+ return 1.0;
+ }
+
+ if (aMinFontSize <= 0) {
+ // No need to scale.
+ return 1.0;
+ }
+
+ // If between this current frame and its font inflation container there is a
+ // non-inline element with fixed width or height, then we should not inflate
+ // fonts for this frame.
+ for (const nsIFrame* f = aFrame; f && !f->IsContainerForFontSizeInflation();
+ f = f->GetParent()) {
+ nsIContent* content = f->GetContent();
+ LayoutFrameType fType = f->Type();
+ nsIFrame* parent = f->GetParent();
+ // Also, if there is more than one frame corresponding to a single
+ // content node, we want the outermost one.
+ if (!(parent && parent->GetContent() == content) &&
+ // ignore width/height on inlines since they don't apply
+ fType != LayoutFrameType::Inline &&
+ // ignore width on radios and checkboxes since we enlarge them and
+ // they have width/height in ua.css
+ fType != LayoutFrameType::CheckboxRadio) {
+ // ruby annotations should have the same inflation as its
+ // grandparent, which is the ruby frame contains the annotation.
+ if (fType == LayoutFrameType::RubyText) {
+ MOZ_ASSERT(parent && parent->IsRubyTextContainerFrame());
+ nsIFrame* grandparent = parent->GetParent();
+ MOZ_ASSERT(grandparent && grandparent->IsRubyFrame());
+ return FontSizeInflationFor(grandparent);
+ }
+ WritingMode wm = f->GetWritingMode();
+ const auto& stylePosISize = f->StylePosition()->ISize(wm);
+ const auto& stylePosBSize = f->StylePosition()->BSize(wm);
+ if (!stylePosISize.IsAuto() ||
+ !stylePosBSize.BehavesLikeInitialValueOnBlockAxis()) {
+ return 1.0;
+ }
+ }
+ }
+
+ int32_t interceptParam = StaticPrefs::font_size_inflation_mappingIntercept();
+ float maxRatio = (float)StaticPrefs::font_size_inflation_maxRatio() / 100.0f;
+
+ float ratio = float(styleFontSize) / float(aMinFontSize);
+ float inflationRatio;
+
+ // Given a minimum inflated font size m, a specified font size s, we want to
+ // find the inflated font size i and then return the ratio of i to s (i/s).
+ if (interceptParam >= 0) {
+ // Since the mapping intercept parameter P is greater than zero, we use it
+ // to determine the point where our mapping function intersects the i=s
+ // line. This means that we have an equation of the form:
+ //
+ // i = m + s*(P/2)/(1 + P/2), if s <= (1 + P/2)*m
+ // i = s, if s >= (1 + P/2)*m
+
+ float intercept = 1 + float(interceptParam) / 2.0f;
+ if (ratio >= intercept) {
+ // If we're already at 1+P/2 or more times the minimum, don't scale.
+ return 1.0;
+ }
+
+ // The point (intercept, intercept) is where the part of the i vs. s graph
+ // that's not slope 1 meets the i=s line. (This part of the
+ // graph is a line from (0, m), to that point). We calculate the
+ // intersection point to be ((1+P/2)m, (1+P/2)m), where P is the
+ // intercept parameter above. We then need to return i/s.
+ inflationRatio = (1.0f + (ratio * (intercept - 1) / intercept)) / ratio;
+ } else {
+ // This is the case where P is negative. We essentially want to implement
+ // the case for P=infinity here, so we make i = s + m, which means that
+ // i/s = s/s + m/s = 1 + 1/ratio
+ inflationRatio = 1 + 1.0f / ratio;
+ }
+
+ if (maxRatio > 1.0 && inflationRatio > maxRatio) {
+ return maxRatio;
+ } else {
+ return inflationRatio;
+ }
+}
+
+static bool ShouldInflateFontsForContainer(const nsIFrame* aFrame) {
+ // We only want to inflate fonts for text that is in a place
+ // with room to expand. The question is what the best heuristic for
+ // that is...
+ // For now, we're going to use NS_FRAME_IN_CONSTRAINED_BSIZE, which
+ // indicates whether the frame is inside something with a constrained
+ // block-size (propagating down the tree), but the propagation stops when
+ // we hit overflow-y [or -x, for vertical mode]: scroll or auto.
+ const nsStyleText* styleText = aFrame->StyleText();
+
+ return styleText->mTextSizeAdjust != StyleTextSizeAdjust::None &&
+ !aFrame->HasAnyStateBits(NS_FRAME_IN_CONSTRAINED_BSIZE) &&
+ // We also want to disable font inflation for containers that have
+ // preformatted text.
+ // MathML cells need special treatment. See bug 1002526 comment 56.
+ (styleText->WhiteSpaceCanWrap(aFrame) || aFrame->IsMathMLFrame());
+}
+
+nscoord nsLayoutUtils::InflationMinFontSizeFor(const nsIFrame* aFrame) {
+ nsPresContext* presContext = aFrame->PresContext();
+ if (!FontSizeInflationEnabled(presContext) ||
+ presContext->mInflationDisabledForShrinkWrap) {
+ return 0;
+ }
+
+ for (const nsIFrame* f = aFrame; f; f = f->GetParent()) {
+ if (f->IsContainerForFontSizeInflation()) {
+ if (!ShouldInflateFontsForContainer(f)) {
+ return 0;
+ }
+
+ nsFontInflationData* data =
+ nsFontInflationData::FindFontInflationDataFor(aFrame);
+ // FIXME: The need to null-check here is sort of a bug, and might
+ // lead to incorrect results.
+ if (!data || !data->InflationEnabled()) {
+ return 0;
+ }
+
+ return MinimumFontSizeFor(aFrame->PresContext(), aFrame->GetWritingMode(),
+ data->UsableISize());
+ }
+ }
+
+ MOZ_ASSERT(false, "root should always be container");
+
+ return 0;
+}
+
+float nsLayoutUtils::FontSizeInflationFor(const nsIFrame* aFrame) {
+ if (aFrame->IsInSVGTextSubtree()) {
+ const nsIFrame* container = aFrame;
+ while (!container->IsSVGTextFrame()) {
+ container = container->GetParent();
+ }
+ NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
+ return static_cast<const SVGTextFrame*>(container)
+ ->GetFontSizeScaleFactor();
+ }
+
+ if (!FontSizeInflationEnabled(aFrame->PresContext())) {
+ return 1.0f;
+ }
+
+ return FontSizeInflationInner(aFrame, InflationMinFontSizeFor(aFrame));
+}
+
+/* static */
+bool nsLayoutUtils::FontSizeInflationEnabled(nsPresContext* aPresContext) {
+ PresShell* presShell = aPresContext->GetPresShell();
+ if (!presShell) {
+ return false;
+ }
+ return presShell->FontSizeInflationEnabled();
+}
+
+/* static */
+nsRect nsLayoutUtils::GetBoxShadowRectForFrame(nsIFrame* aFrame,
+ const nsSize& aFrameSize) {
+ auto boxShadows = aFrame->StyleEffects()->mBoxShadow.AsSpan();
+ if (boxShadows.IsEmpty()) {
+ return nsRect();
+ }
+
+ nsRect inputRect(nsPoint(0, 0), aFrameSize);
+
+ // According to the CSS spec, box-shadow should be based on the border box.
+ // However, that looks broken when the background extends outside the border
+ // box, as can be the case with native theming. To fix that we expand the
+ // area that we shadow to include the bounds of any native theme drawing.
+ const nsStyleDisplay* styleDisplay = aFrame->StyleDisplay();
+ nsITheme::Transparency transparency;
+ if (aFrame->IsThemed(styleDisplay, &transparency)) {
+ // For opaque (rectangular) theme widgets we can take the generic
+ // border-box path with border-radius disabled.
+ if (transparency != nsITheme::eOpaque) {
+ nsPresContext* presContext = aFrame->PresContext();
+ presContext->Theme()->GetWidgetOverflow(
+ presContext->DeviceContext(), aFrame,
+ styleDisplay->EffectiveAppearance(), &inputRect);
+ }
+ }
+
+ nsRect shadows;
+ int32_t A2D = aFrame->PresContext()->AppUnitsPerDevPixel();
+ for (auto& shadow : boxShadows) {
+ nsRect tmpRect = inputRect;
+
+ // inset shadows are never painted outside the frame
+ if (shadow.inset) {
+ continue;
+ }
+
+ tmpRect.MoveBy(nsPoint(shadow.base.horizontal.ToAppUnits(),
+ shadow.base.vertical.ToAppUnits()));
+ tmpRect.Inflate(shadow.spread.ToAppUnits());
+ tmpRect.Inflate(nsContextBoxBlur::GetBlurRadiusMargin(
+ shadow.base.blur.ToAppUnits(), A2D));
+ shadows.UnionRect(shadows, tmpRect);
+ }
+ return shadows;
+}
+
+/* static */
+bool nsLayoutUtils::GetDocumentViewerSize(
+ const nsPresContext* aPresContext, LayoutDeviceIntSize& aOutSize,
+ SubtractDynamicToolbar aSubtractDynamicToolbar) {
+ nsCOMPtr<nsIDocShell> docShell = aPresContext->GetDocShell();
+ if (!docShell) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ docShell->GetDocViewer(getter_AddRefs(viewer));
+ if (!viewer) {
+ return false;
+ }
+
+ nsIntRect bounds;
+ viewer->GetBounds(bounds);
+
+ if (aPresContext->IsRootContentDocumentCrossProcess() &&
+ aSubtractDynamicToolbar == SubtractDynamicToolbar::Yes &&
+ aPresContext->HasDynamicToolbar() && !bounds.IsEmpty()) {
+ MOZ_ASSERT(aPresContext->IsRootContentDocumentCrossProcess());
+ bounds.height -= aPresContext->GetDynamicToolbarMaxHeight();
+ // Collapse the size in the case the dynamic toolbar max height is greater
+ // than the content bound height so that hopefully embedders of GeckoView
+ // may notice they set wrong dynamic toolbar max height.
+ if (bounds.height < 0) {
+ bounds.height = 0;
+ }
+ }
+
+ aOutSize = LayoutDeviceIntRect::FromUnknownRect(bounds).Size();
+ return true;
+}
+
+bool nsLayoutUtils::UpdateCompositionBoundsForRCDRSF(
+ ParentLayerRect& aCompBounds, const nsPresContext* aPresContext,
+ IncludeDynamicToolbar aIncludeDynamicToolbar) {
+ SubtractDynamicToolbar shouldSubtractDynamicToolbar =
+ aIncludeDynamicToolbar == IncludeDynamicToolbar::Force
+ ? SubtractDynamicToolbar::No
+ : aPresContext->IsRootContentDocumentCrossProcess() &&
+ aPresContext->HasDynamicToolbar()
+ ? SubtractDynamicToolbar::Yes
+ : SubtractDynamicToolbar::No;
+
+ if (shouldSubtractDynamicToolbar == SubtractDynamicToolbar::Yes) {
+ if (RefPtr<MobileViewportManager> MVM =
+ aPresContext->PresShell()->GetMobileViewportManager()) {
+ // Convert the intrinsic composition size to app units here since
+ // the returned size of below CalculateScrollableRectForFrame call has
+ // been already converted/rounded to app units.
+ nsSize intrinsicCompositionSize =
+ CSSSize::ToAppUnits(MVM->GetIntrinsicCompositionSize());
+
+ if (nsIScrollableFrame* rootScrollableFrame =
+ aPresContext->PresShell()->GetRootScrollFrameAsScrollable()) {
+ // Expand the composition size to include the area initially covered by
+ // the dynamic toolbar only if the content is taller than the intrinsic
+ // composition size (i.e. the dynamic toolbar should be able to move
+ // only if the content is vertically scrollable).
+ if (intrinsicCompositionSize.height <
+ CalculateScrollableRectForFrame(rootScrollableFrame, nullptr)
+ .Height()) {
+ shouldSubtractDynamicToolbar = SubtractDynamicToolbar::No;
+ }
+ }
+ }
+ }
+
+ LayoutDeviceIntSize contentSize;
+ if (!GetDocumentViewerSize(aPresContext, contentSize,
+ shouldSubtractDynamicToolbar)) {
+ return false;
+ }
+ aCompBounds.SizeTo(ViewAs<ParentLayerPixel>(
+ LayoutDeviceSize(contentSize),
+ PixelCastJustification::LayoutDeviceIsParentLayerForRCDRSF));
+ return true;
+}
+
+/* static */
+nsMargin nsLayoutUtils::ScrollbarAreaToExcludeFromCompositionBoundsFor(
+ const nsIFrame* aScrollFrame) {
+ if (!aScrollFrame || !aScrollFrame->GetScrollTargetFrame()) {
+ return nsMargin();
+ }
+ nsPresContext* presContext = aScrollFrame->PresContext();
+ PresShell* presShell = presContext->GetPresShell();
+ if (!presShell) {
+ return nsMargin();
+ }
+ bool isRootScrollFrame = aScrollFrame == presShell->GetRootScrollFrame();
+ bool isRootContentDocRootScrollFrame =
+ isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
+ if (!isRootContentDocRootScrollFrame) {
+ return nsMargin();
+ }
+ if (presContext->UseOverlayScrollbars()) {
+ return nsMargin();
+ }
+ nsIScrollableFrame* scrollableFrame = aScrollFrame->GetScrollTargetFrame();
+ if (!scrollableFrame) {
+ return nsMargin();
+ }
+ return scrollableFrame->GetActualScrollbarSizes(
+ nsIScrollableFrame::ScrollbarSizesOptions::
+ INCLUDE_VISUAL_VIEWPORT_SCROLLBARS);
+}
+
+/* static */
+nsSize nsLayoutUtils::CalculateCompositionSizeForFrame(
+ nsIFrame* aFrame, bool aSubtractScrollbars,
+ const nsSize* aOverrideScrollPortSize,
+ IncludeDynamicToolbar aIncludeDynamicToolbar) {
+ // If we have a scrollable frame, restrict the composition bounds to its
+ // scroll port. The scroll port excludes the frame borders and the scroll
+ // bars, which we don't want to be part of the composition bounds.
+ nsIScrollableFrame* scrollableFrame = aFrame->GetScrollTargetFrame();
+ nsRect rect = scrollableFrame ? scrollableFrame->GetScrollPortRect()
+ : aFrame->GetRect();
+ nsSize size =
+ aOverrideScrollPortSize ? *aOverrideScrollPortSize : rect.Size();
+
+ nsPresContext* presContext = aFrame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+
+ bool isRootContentDocRootScrollFrame =
+ presContext->IsRootContentDocumentCrossProcess() &&
+ aFrame == presShell->GetRootScrollFrame();
+ if (isRootContentDocRootScrollFrame) {
+ ParentLayerRect compBounds;
+ if (UpdateCompositionBoundsForRCDRSF(compBounds, presContext,
+ aIncludeDynamicToolbar)) {
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+ size = nsSize(compBounds.width * auPerDevPixel,
+ compBounds.height * auPerDevPixel);
+ }
+ }
+
+ if (aSubtractScrollbars) {
+ nsMargin margins = ScrollbarAreaToExcludeFromCompositionBoundsFor(aFrame);
+ size.width -= margins.LeftRight();
+ size.height -= margins.TopBottom();
+ }
+
+ return size;
+}
+
+/* static */
+CSSSize nsLayoutUtils::CalculateBoundingCompositionSize(
+ const nsIFrame* aFrame, bool aIsRootContentDocRootScrollFrame,
+ const FrameMetrics& aMetrics) {
+ if (aIsRootContentDocRootScrollFrame) {
+ return ViewAs<LayerPixel>(
+ aMetrics.GetCompositionBounds().Size(),
+ PixelCastJustification::ParentLayerToLayerForRootComposition) *
+ LayerToScreenScale(1.0f) / aMetrics.DisplayportPixelsPerCSSPixel();
+ }
+ nsPresContext* presContext = aFrame->PresContext();
+ ScreenSize rootCompositionSize;
+ nsPresContext* rootPresContext =
+ presContext->GetInProcessRootContentDocumentPresContext();
+ if (!rootPresContext) {
+ rootPresContext = presContext->GetRootPresContext();
+ }
+ PresShell* rootPresShell = nullptr;
+ if (rootPresContext) {
+ rootPresShell = rootPresContext->PresShell();
+ if (nsIFrame* rootFrame = rootPresShell->GetRootFrame()) {
+ ParentLayerRect compBounds;
+ if (UpdateCompositionBoundsForRCDRSF(compBounds, rootPresContext)) {
+ rootCompositionSize = ViewAs<ScreenPixel>(
+ compBounds.Size(),
+ PixelCastJustification::ScreenIsParentLayerForRoot);
+ } else {
+ // LayoutDeviceToScreenScale2D =
+ // LayoutDeviceToParentLayerScale *
+ // ParentLayerToScreenScale2D
+ LayoutDeviceToScreenScale2D cumulativeResolution =
+ LayoutDeviceToParentLayerScale(
+ rootPresShell->GetCumulativeResolution()) *
+ GetTransformToAncestorScaleCrossProcessForFrameMetrics(rootFrame);
+
+ int32_t rootAUPerDevPixel = rootPresContext->AppUnitsPerDevPixel();
+ rootCompositionSize = (LayoutDeviceRect::FromAppUnits(
+ rootFrame->GetRect(), rootAUPerDevPixel) *
+ cumulativeResolution)
+ .Size();
+ }
+ }
+ } else {
+ nsIWidget* widget = aFrame->GetNearestWidget();
+ LayoutDeviceIntRect widgetBounds = widget->GetBounds();
+ rootCompositionSize = ScreenSize(ViewAs<ScreenPixel>(
+ widgetBounds.Size(),
+ PixelCastJustification::LayoutDeviceIsScreenForBounds));
+ }
+
+ // Adjust composition size for the size of scroll bars.
+ nsIFrame* rootRootScrollFrame =
+ rootPresShell ? rootPresShell->GetRootScrollFrame() : nullptr;
+ nsMargin scrollbarMargins =
+ ScrollbarAreaToExcludeFromCompositionBoundsFor(rootRootScrollFrame);
+ LayoutDeviceMargin margins = LayoutDeviceMargin::FromAppUnits(
+ scrollbarMargins, rootPresContext->AppUnitsPerDevPixel());
+ // Scrollbars are not subject to resolution scaling, so LD pixels = layer
+ // pixels for them.
+ rootCompositionSize.width -= margins.LeftRight();
+ rootCompositionSize.height -= margins.TopBottom();
+
+ CSSSize result =
+ rootCompositionSize / aMetrics.DisplayportPixelsPerCSSPixel();
+
+ // If this is a nested content process, the in-process root content document's
+ // composition size may still be arbitrarily large, so bound it further by
+ // how much of the in-process RCD is visible in the top-level (cross-process
+ // RCD) viewport.
+ if (rootPresShell) {
+ if (BrowserChild* bc = BrowserChild::GetFrom(rootPresShell)) {
+ if (const auto& visibleRect =
+ bc->GetTopLevelViewportVisibleRectInSelfCoords()) {
+ CSSSize cssVisibleRect =
+ visibleRect->Size() / rootPresContext->CSSToDevPixelScale();
+ result = Min(result, cssVisibleRect);
+ }
+ }
+ }
+
+ return result;
+}
+
+/* static */
+nsRect nsLayoutUtils::CalculateScrollableRectForFrame(
+ const nsIScrollableFrame* aScrollableFrame, const nsIFrame* aRootFrame) {
+ nsRect contentBounds;
+ if (aScrollableFrame) {
+ contentBounds = aScrollableFrame->GetScrollRange();
+
+ nsPoint scrollPosition = aScrollableFrame->GetScrollPosition();
+ if (aScrollableFrame->GetScrollStyles().mVertical ==
+ StyleOverflow::Hidden) {
+ contentBounds.y = scrollPosition.y;
+ contentBounds.height = 0;
+ }
+ if (aScrollableFrame->GetScrollStyles().mHorizontal ==
+ StyleOverflow::Hidden) {
+ contentBounds.x = scrollPosition.x;
+ contentBounds.width = 0;
+ }
+
+ contentBounds.width += aScrollableFrame->GetScrollPortRect().width;
+ contentBounds.height += aScrollableFrame->GetScrollPortRect().height;
+ } else {
+ contentBounds = aRootFrame->GetRect();
+ // Clamp to (0, 0) if there is no corresponding scrollable frame for the
+ // given |aRootFrame|.
+ contentBounds.MoveTo(0, 0);
+ }
+ return contentBounds;
+}
+
+/* static */
+nsRect nsLayoutUtils::CalculateExpandedScrollableRect(nsIFrame* aFrame) {
+ nsRect scrollableRect = CalculateScrollableRectForFrame(
+ aFrame->GetScrollTargetFrame(), aFrame->PresShell()->GetRootFrame());
+ nsSize compSize = CalculateCompositionSizeForFrame(aFrame);
+
+ if (aFrame == aFrame->PresShell()->GetRootScrollFrame()) {
+ // the composition size for the root scroll frame does not include the
+ // local resolution, so we adjust.
+ float res = aFrame->PresShell()->GetResolution();
+ compSize.width = NSToCoordRound(compSize.width / res);
+ compSize.height = NSToCoordRound(compSize.height / res);
+ }
+
+ if (scrollableRect.width < compSize.width) {
+ scrollableRect.x =
+ std::max(0, scrollableRect.x - (compSize.width - scrollableRect.width));
+ scrollableRect.width = compSize.width;
+ }
+
+ if (scrollableRect.height < compSize.height) {
+ scrollableRect.y = std::max(
+ 0, scrollableRect.y - (compSize.height - scrollableRect.height));
+ scrollableRect.height = compSize.height;
+ }
+ return scrollableRect;
+}
+
+/* static */
+void nsLayoutUtils::DoLogTestDataForPaint(WebRenderLayerManager* aManager,
+ ViewID aScrollId,
+ const std::string& aKey,
+ const std::string& aValue) {
+ MOZ_ASSERT(nsLayoutUtils::IsAPZTestLoggingEnabled(), "don't call me");
+ aManager->LogTestDataForCurrentPaint(aScrollId, aKey, aValue);
+}
+
+void nsLayoutUtils::LogAdditionalTestData(nsDisplayListBuilder* aBuilder,
+ const std::string& aKey,
+ const std::string& aValue) {
+ WebRenderLayerManager* manager = aBuilder->GetWidgetLayerManager(nullptr);
+ if (!manager) {
+ return;
+ }
+ manager->LogAdditionalTestData(aKey, aValue);
+}
+
+/* static */
+bool nsLayoutUtils::IsAPZTestLoggingEnabled() {
+ return StaticPrefs::apz_test_logging_enabled();
+}
+
+////////////////////////////////////////
+// SurfaceFromElementResult
+
+SurfaceFromElementResult::SurfaceFromElementResult()
+ // Use safe default values here
+ : mHadCrossOriginRedirects(false),
+ mIsWriteOnly(true),
+ mIsStillLoading(false),
+ mHasSize(false),
+ mCORSUsed(false),
+ mAlphaType(gfxAlphaType::Opaque) {}
+
+const RefPtr<mozilla::gfx::SourceSurface>&
+SurfaceFromElementResult::GetSourceSurface() {
+ if (!mSourceSurface && mLayersImage) {
+ mSourceSurface = mLayersImage->GetAsSourceSurface();
+ }
+
+ return mSourceSurface;
+}
+
+////////////////////////////////////////
+
+bool nsLayoutUtils::IsNonWrapperBlock(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ return aFrame->IsBlockFrameOrSubclass() && !aFrame->IsBlockWrapper();
+}
+
+AutoMaybeDisableFontInflation::AutoMaybeDisableFontInflation(nsIFrame* aFrame) {
+ // FIXME: Now that inflation calculations are based on the flow
+ // root's NCA's (nearest common ancestor of its inflatable
+ // descendants) width, we could probably disable inflation in
+ // fewer cases than we currently do.
+ // MathML cells need special treatment. See bug 1002526 comment 56.
+ if (aFrame->IsContainerForFontSizeInflation() && !aFrame->IsMathMLFrame()) {
+ mPresContext = aFrame->PresContext();
+ mOldValue = mPresContext->mInflationDisabledForShrinkWrap;
+ mPresContext->mInflationDisabledForShrinkWrap = true;
+ } else {
+ // indicate we have nothing to restore
+ mPresContext = nullptr;
+ mOldValue = false;
+ }
+}
+
+AutoMaybeDisableFontInflation::~AutoMaybeDisableFontInflation() {
+ if (mPresContext) {
+ mPresContext->mInflationDisabledForShrinkWrap = mOldValue;
+ }
+}
+
+namespace mozilla {
+
+Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel) {
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ return Rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+}
+
+Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT) {
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ Rect rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+ MaybeSnapToDevicePixels(rect, aSnapDT, true);
+ return rect;
+}
+// Similar to a snapped rect, except an axis is left unsnapped if the snapping
+// process results in a length of 0.
+Rect NSRectToNonEmptySnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT) {
+ // Note that by making aAppUnitsPerPixel a double we're doing floating-point
+ // division using a larger type and avoiding rounding error.
+ Rect rect(Float(aRect.x / aAppUnitsPerPixel),
+ Float(aRect.y / aAppUnitsPerPixel),
+ Float(aRect.width / aAppUnitsPerPixel),
+ Float(aRect.height / aAppUnitsPerPixel));
+ MaybeSnapToDevicePixels(rect, aSnapDT, true, false);
+ return rect;
+}
+
+void StrokeLineWithSnapping(const nsPoint& aP1, const nsPoint& aP2,
+ int32_t aAppUnitsPerDevPixel,
+ DrawTarget& aDrawTarget, const Pattern& aPattern,
+ const StrokeOptions& aStrokeOptions,
+ const DrawOptions& aDrawOptions) {
+ Point p1 = NSPointToPoint(aP1, aAppUnitsPerDevPixel);
+ Point p2 = NSPointToPoint(aP2, aAppUnitsPerDevPixel);
+ SnapLineToDevicePixelsForStroking(p1, p2, aDrawTarget,
+ aStrokeOptions.mLineWidth);
+ aDrawTarget.StrokeLine(p1, p2, aPattern, aStrokeOptions, aDrawOptions);
+}
+
+} // namespace mozilla
+
+/* static */
+void nsLayoutUtils::SetBSizeFromFontMetrics(const nsIFrame* aFrame,
+ ReflowOutput& aMetrics,
+ const LogicalMargin& aFramePadding,
+ WritingMode aLineWM,
+ WritingMode aFrameWM) {
+ RefPtr<nsFontMetrics> fm =
+ nsLayoutUtils::GetInflatedFontMetricsForFrame(aFrame);
+
+ if (fm) {
+ // Compute final height of the frame.
+ //
+ // Do things the standard css2 way -- though it's hard to find it
+ // in the css2 spec! It's actually found in the css1 spec section
+ // 4.4 (you will have to read between the lines to really see
+ // it).
+ //
+ // The height of our box is the sum of our font size plus the top
+ // and bottom border and padding. The height of children do not
+ // affect our height.
+ aMetrics.SetBlockStartAscent(
+ aLineWM.IsAlphabeticalBaseline()
+ ? aLineWM.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent()
+ : fm->MaxHeight() / 2);
+ aMetrics.BSize(aLineWM) = fm->MaxHeight();
+ } else {
+ NS_WARNING("Cannot get font metrics - defaulting sizes to 0");
+ aMetrics.SetBlockStartAscent(aMetrics.BSize(aLineWM) = 0);
+ }
+ aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
+ aFramePadding.BStart(aFrameWM));
+ aMetrics.BSize(aLineWM) += aFramePadding.BStartEnd(aFrameWM);
+}
+
+/* static */
+// _BOUNDARY because Dispatch() with `targets` must not handle the event.
+MOZ_CAN_RUN_SCRIPT_BOUNDARY bool
+nsLayoutUtils::HasDocumentLevelListenersForApzAwareEvents(
+ PresShell* aPresShell) {
+ if (RefPtr<Document> doc = aPresShell->GetDocument()) {
+ WidgetEvent event(true, eVoidEvent);
+ nsTArray<EventTarget*> targets;
+ nsresult rv = EventDispatcher::Dispatch(doc, nullptr, &event, nullptr,
+ nullptr, nullptr, &targets);
+ NS_ENSURE_SUCCESS(rv, false);
+ for (size_t i = 0; i < targets.Length(); i++) {
+ if (targets[i]->IsApzAware()) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsLayoutUtils::CanScrollOriginClobberApz(ScrollOrigin aScrollOrigin) {
+ switch (aScrollOrigin) {
+ case ScrollOrigin::None:
+ case ScrollOrigin::NotSpecified:
+ case ScrollOrigin::Apz:
+ case ScrollOrigin::Restore:
+ return false;
+ default:
+ return true;
+ }
+}
+
+/* static */
+ScrollMetadata nsLayoutUtils::ComputeScrollMetadata(
+ const nsIFrame* aForFrame, const nsIFrame* aScrollFrame,
+ nsIContent* aContent, const nsIFrame* aItemFrame,
+ const nsPoint& aOffsetToReferenceFrame,
+ WebRenderLayerManager* aLayerManager, ViewID aScrollParentId,
+ const nsSize& aScrollPortSize, bool aIsRootContent) {
+ const nsPresContext* presContext = aForFrame->PresContext();
+ int32_t auPerDevPixel = presContext->AppUnitsPerDevPixel();
+
+ PresShell* presShell = presContext->GetPresShell();
+ ScrollMetadata metadata;
+ FrameMetrics& metrics = metadata.GetMetrics();
+ metrics.SetLayoutViewport(
+ CSSRect(CSSPoint(), CSSSize::FromAppUnits(aScrollPortSize)));
+
+ nsIDocShell* docShell = presContext->GetDocShell();
+ const BrowsingContext* bc =
+ docShell ? docShell->GetBrowsingContext() : nullptr;
+ bool isTouchEventsEnabled =
+ bc &&
+ bc->TouchEventsOverride() == mozilla::dom::TouchEventsOverride::Enabled;
+
+ if (bc && bc->InRDMPane() && isTouchEventsEnabled) {
+ metadata.SetIsRDMTouchSimulationActive(true);
+ }
+
+ ViewID scrollId = ScrollableLayerGuid::NULL_SCROLL_ID;
+ if (aContent) {
+ if (void* paintRequestTime =
+ aContent->GetProperty(nsGkAtoms::paintRequestTime)) {
+ metrics.SetPaintRequestTime(*static_cast<TimeStamp*>(paintRequestTime));
+ aContent->RemoveProperty(nsGkAtoms::paintRequestTime);
+ }
+ scrollId = nsLayoutUtils::FindOrCreateIDFor(aContent);
+ nsRect dp;
+ if (DisplayPortUtils::GetDisplayPort(aContent, &dp)) {
+ metrics.SetDisplayPort(CSSRect::FromAppUnits(dp));
+ DisplayPortUtils::MarkDisplayPortAsPainted(aContent);
+ }
+
+ metrics.SetHasNonZeroDisplayPortMargins(false);
+ if (DisplayPortMarginsPropertyData* currentData =
+ static_cast<DisplayPortMarginsPropertyData*>(
+ aContent->GetProperty(nsGkAtoms::DisplayPortMargins))) {
+ if (currentData->mMargins.mMargins != ScreenMargin()) {
+ metrics.SetHasNonZeroDisplayPortMargins(true);
+ }
+ }
+
+ // Note: GetProperty() will return nullptr both in the case where
+ // the property hasn't been set, and in the case where the property
+ // has been set to false (in which case the property value is
+ // `reinterpret_cast<void*>(false)` which is nullptr.
+ if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodir)) {
+ metadata.SetForceMousewheelAutodir(true);
+ }
+
+ if (aContent->GetProperty(nsGkAtoms::forceMousewheelAutodirHonourRoot)) {
+ metadata.SetForceMousewheelAutodirHonourRoot(true);
+ }
+
+ if (IsAPZTestLoggingEnabled()) {
+ LogTestDataForPaint(aLayerManager, scrollId, "displayport",
+ metrics.GetDisplayPort());
+ }
+
+ metrics.SetMinimalDisplayPort(
+ aContent->GetProperty(nsGkAtoms::MinimalDisplayPort));
+ }
+
+ nsIScrollableFrame* scrollableFrame = nullptr;
+ if (aScrollFrame) scrollableFrame = aScrollFrame->GetScrollTargetFrame();
+
+ metrics.SetScrollableRect(
+ CSSRect::FromAppUnits(nsLayoutUtils::CalculateScrollableRectForFrame(
+ scrollableFrame, aForFrame)));
+
+ if (scrollableFrame) {
+ CSSPoint layoutScrollOffset =
+ CSSPoint::FromAppUnits(scrollableFrame->GetScrollPosition());
+ CSSPoint visualScrollOffset =
+ aIsRootContent
+ ? CSSPoint::FromAppUnits(presShell->GetVisualViewportOffset())
+ : layoutScrollOffset;
+ metrics.SetVisualScrollOffset(visualScrollOffset);
+ // APZ sometimes reads this even if we haven't set a visual scroll
+ // update type (specifically, in the isFirstPaint case), so always
+ // set it.
+ metrics.SetVisualDestination(visualScrollOffset);
+
+ if (aIsRootContent) {
+ if (aLayerManager->GetIsFirstPaint() &&
+ presShell->IsVisualViewportOffsetSet()) {
+ // Restore the visual viewport offset to the copy stored on the
+ // main thread.
+ presShell->ScrollToVisual(presShell->GetVisualViewportOffset(),
+ FrameMetrics::eRestore, ScrollMode::Instant);
+ }
+ }
+
+ if (scrollableFrame->IsRootScrollFrameOfDocument()) {
+ if (const Maybe<PresShell::VisualScrollUpdate>& visualUpdate =
+ presShell->GetPendingVisualScrollUpdate()) {
+ metrics.SetVisualDestination(
+ CSSPoint::FromAppUnits(visualUpdate->mVisualScrollOffset));
+ metrics.SetVisualScrollUpdateType(visualUpdate->mUpdateType);
+ presShell->AcknowledgePendingVisualScrollUpdate();
+ }
+ }
+
+ if (aIsRootContent) {
+ // Expand the layout viewport to the size including the area covered by
+ // the dynamic toolbar in the case where the dynamic toolbar is being
+ // used, otherwise when the dynamic toolbar transitions on the compositor,
+ // the layout viewport will be smaller than the visual viewport on the
+ // compositor, thus the layout viewport offset will be forced to be moved
+ // in FrameMetrics::KeepLayoutViewportEnclosingVisualViewport.
+ if (presContext->HasDynamicToolbar()) {
+ CSSRect viewport = metrics.GetLayoutViewport();
+ viewport.SizeTo(nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ presContext, viewport.Size()));
+ metrics.SetLayoutViewport(viewport);
+
+ // We need to set 'fixed margins' to adjust 'fixed margins' value on the
+ // composiutor in the case where the dynamic toolbar is completely
+ // hidden because the margin value on the compositor is offset from the
+ // position where the dynamic toolbar is completely VISIBLE but now the
+ // toolbar is completely HIDDEN we need to adjust the difference on the
+ // compositor.
+ if (presContext->GetDynamicToolbarState() ==
+ DynamicToolbarState::Collapsed) {
+ metrics.SetFixedLayerMargins(ScreenMargin(
+ 0, 0,
+ ScreenCoord(presContext->GetDynamicToolbarHeight() -
+ presContext->GetDynamicToolbarMaxHeight()),
+ 0));
+ }
+ }
+ }
+
+ metrics.SetScrollGeneration(scrollableFrame->CurrentScrollGeneration());
+
+ CSSRect viewport = metrics.GetLayoutViewport();
+ viewport.MoveTo(layoutScrollOffset);
+ metrics.SetLayoutViewport(viewport);
+
+ nsSize lineScrollAmount = scrollableFrame->GetLineScrollAmount();
+ LayoutDeviceIntSize lineScrollAmountInDevPixels =
+ LayoutDeviceIntSize::FromAppUnitsRounded(
+ lineScrollAmount, presContext->AppUnitsPerDevPixel());
+ metadata.SetLineScrollAmount(lineScrollAmountInDevPixels);
+
+ nsSize pageScrollAmount = scrollableFrame->GetPageScrollAmount();
+ LayoutDeviceIntSize pageScrollAmountInDevPixels =
+ LayoutDeviceIntSize::FromAppUnitsRounded(
+ pageScrollAmount, presContext->AppUnitsPerDevPixel());
+ metadata.SetPageScrollAmount(pageScrollAmountInDevPixels);
+
+ if (aScrollFrame->GetParent()) {
+ metadata.SetDisregardedDirection(
+ WheelHandlingUtils::GetDisregardedWheelScrollDirection(
+ aScrollFrame->GetParent()));
+ }
+
+ metadata.SetSnapInfo(scrollableFrame->GetScrollSnapInfo());
+ metadata.SetOverscrollBehavior(
+ scrollableFrame->GetOverscrollBehaviorInfo());
+ metadata.SetScrollUpdates(scrollableFrame->GetScrollUpdates());
+ }
+
+ // If we have the scrollparent being the same as the scroll id, the
+ // compositor-side code could get into an infinite loop while building the
+ // overscroll handoff chain.
+ MOZ_ASSERT(aScrollParentId == ScrollableLayerGuid::NULL_SCROLL_ID ||
+ scrollId != aScrollParentId);
+ metrics.SetScrollId(scrollId);
+ metrics.SetIsRootContent(aIsRootContent);
+ metadata.SetScrollParentId(aScrollParentId);
+
+ const nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ bool isRootScrollFrame = aScrollFrame == rootScrollFrame;
+ Document* document = presShell->GetDocument();
+
+ if (scrollId != ScrollableLayerGuid::NULL_SCROLL_ID &&
+ !presContext->GetParentPresContext()) {
+ if ((aScrollFrame && isRootScrollFrame)) {
+ metadata.SetIsLayersIdRoot(true);
+ } else {
+ MOZ_ASSERT(document, "A non-root-scroll frame must be in a document");
+ if (aContent == document->GetDocumentElement()) {
+ metadata.SetIsLayersIdRoot(true);
+ }
+ }
+ }
+
+ // Get whether the root content is RTL(E.g. it's true either if
+ // "writing-mode: vertical-rl", or if
+ // "writing-mode: horizontal-tb; direction: rtl;" in CSS).
+ // For the concept of this and the reason why we need to get this kind of
+ // information, see the definition of |mIsAutoDirRootContentRTL| in struct
+ // |ScrollMetadata|.
+ const Element* bodyElement = document ? document->GetBodyElement() : nullptr;
+ const nsIFrame* primaryFrame =
+ bodyElement ? bodyElement->GetPrimaryFrame() : rootScrollFrame;
+ if (!primaryFrame) {
+ primaryFrame = rootScrollFrame;
+ }
+ if (primaryFrame) {
+ WritingMode writingModeOfRootScrollFrame = primaryFrame->GetWritingMode();
+ WritingMode::BlockDir blockDirOfRootScrollFrame =
+ writingModeOfRootScrollFrame.GetBlockDir();
+ WritingMode::InlineDir inlineDirOfRootScrollFrame =
+ writingModeOfRootScrollFrame.GetInlineDir();
+ if (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockRL ||
+ (blockDirOfRootScrollFrame == WritingMode::BlockDir::eBlockTB &&
+ inlineDirOfRootScrollFrame == WritingMode::InlineDir::eInlineRTL)) {
+ metadata.SetIsAutoDirRootContentRTL(true);
+ }
+ }
+
+ // Only the root scrollable frame for a given presShell should pick up
+ // the presShell's resolution. All the other frames are 1.0.
+ if (isRootScrollFrame) {
+ metrics.SetPresShellResolution(presShell->GetResolution());
+ } else {
+ metrics.SetPresShellResolution(1.0f);
+ }
+
+ if (presShell->IsResolutionUpdated()) {
+ metadata.SetResolutionUpdated(true);
+ }
+
+ // The cumulative resolution is the resolution at which the scroll frame's
+ // content is actually rendered. It includes the pres shell resolutions of
+ // all the pres shells from here up to the root, as well as any css-driven
+ // resolution. We don't need to compute it as it's already stored in the
+ // container parameters... except if we're in WebRender in which case we
+ // don't have a aContainerParameters. In that case we're also not rasterizing
+ // in Gecko anyway, so the only resolution we care about here is the presShell
+ // resolution which we need to propagate to WebRender.
+ metrics.SetCumulativeResolution(
+ LayoutDeviceToLayerScale(presShell->GetCumulativeResolution()));
+
+ metrics.SetTransformToAncestorScale(
+ GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ aScrollFrame ? aScrollFrame : aForFrame));
+ metrics.SetDevPixelsPerCSSPixel(presContext->CSSToDevPixelScale());
+
+ // Initially, AsyncPanZoomController should render the content to the screen
+ // at the painted resolution.
+ const LayerToParentLayerScale layerToParentLayerScale(1.0f);
+ metrics.SetZoom(metrics.GetCumulativeResolution() *
+ metrics.GetDevPixelsPerCSSPixel() * layerToParentLayerScale);
+
+ // Calculate the composition bounds as the size of the scroll frame and
+ // its origin relative to the reference frame.
+ // If aScrollFrame is null, we are in a document without a root scroll frame,
+ // so it's a xul document. In this case, use the size of the viewport frame.
+ const nsIFrame* frameForCompositionBoundsCalculation =
+ aScrollFrame ? aScrollFrame : aForFrame;
+ nsRect compositionBounds(
+ frameForCompositionBoundsCalculation->GetOffsetToCrossDoc(aItemFrame) +
+ aOffsetToReferenceFrame,
+ frameForCompositionBoundsCalculation->GetSize());
+ if (scrollableFrame) {
+ // If we have a scrollable frame, restrict the composition bounds to its
+ // scroll port. The scroll port excludes the frame borders and the scroll
+ // bars, which we don't want to be part of the composition bounds.
+ nsRect scrollPort = scrollableFrame->GetScrollPortRect();
+ compositionBounds = nsRect(
+ compositionBounds.TopLeft() + scrollPort.TopLeft(), scrollPort.Size());
+ }
+ ParentLayerRect frameBounds =
+ LayoutDeviceRect::FromAppUnits(compositionBounds, auPerDevPixel) *
+ metrics.GetCumulativeResolution() * layerToParentLayerScale;
+
+ // For the root scroll frame of the root content document (RCD-RSF), the above
+ // calculation will yield the size of the viewport frame as the composition
+ // bounds, which doesn't actually correspond to what is visible when
+ // nsIDOMWindowUtils::setCSSViewport has been called to modify the visible
+ // area of the prescontext that the viewport frame is reflowed into. In that
+ // case if our document has a widget then the widget's bounds will correspond
+ // to what is visible. If we don't have a widget the root view's bounds
+ // correspond to what would be visible because they don't get modified by
+ // setCSSViewport.
+ bool isRootContentDocRootScrollFrame =
+ isRootScrollFrame && presContext->IsRootContentDocumentCrossProcess();
+ if (isRootContentDocRootScrollFrame) {
+ UpdateCompositionBoundsForRCDRSF(frameBounds, presContext);
+ if (RefPtr<MobileViewportManager> MVM =
+ presContext->PresShell()->GetMobileViewportManager()) {
+ metrics.SetCompositionSizeWithoutDynamicToolbar(
+ MVM->GetCompositionSizeWithoutDynamicToolbar());
+ }
+ }
+
+ metrics.SetCompositionBoundsWidthIgnoringScrollbars(frameBounds.width);
+
+ nsMargin sizes = ScrollbarAreaToExcludeFromCompositionBoundsFor(aScrollFrame);
+ // Scrollbars are not subject to resolution scaling, so LD pixels = layer
+ // pixels for them.
+ ParentLayerMargin boundMargins =
+ LayoutDeviceMargin::FromAppUnits(sizes, auPerDevPixel) *
+ LayoutDeviceToParentLayerScale(1.0f);
+ frameBounds.Deflate(boundMargins);
+
+ metrics.SetCompositionBounds(frameBounds);
+
+ metrics.SetBoundingCompositionSize(
+ nsLayoutUtils::CalculateBoundingCompositionSize(
+ aScrollFrame ? aScrollFrame : aForFrame,
+ isRootContentDocRootScrollFrame, metrics));
+
+ if (StaticPrefs::apz_printtree() || StaticPrefs::apz_test_logging_enabled()) {
+ if (const nsIContent* content =
+ frameForCompositionBoundsCalculation->GetContent()) {
+ nsAutoString contentDescription;
+ if (content->IsElement()) {
+ content->AsElement()->Describe(contentDescription);
+ } else {
+ contentDescription.AssignLiteral("(not an element)");
+ }
+ metadata.SetContentDescription(
+ NS_LossyConvertUTF16toASCII(contentDescription));
+ if (IsAPZTestLoggingEnabled()) {
+ LogTestDataForPaint(aLayerManager, scrollId, "contentDescription",
+ metadata.GetContentDescription().get());
+ }
+ }
+ }
+
+ metrics.SetPresShellId(presShell->GetPresShellId());
+
+ // If the scroll frame's content is marked 'scrollgrab', record this
+ // in the FrameMetrics so APZ knows to provide the scroll grabbing
+ // behaviour.
+ if (aScrollFrame &&
+ nsContentUtils::HasScrollgrab(aScrollFrame->GetContent())) {
+ metadata.SetHasScrollgrab(true);
+ }
+
+ if (ShouldDisableApzForElement(aContent)) {
+ metadata.SetForceDisableApz(true);
+ }
+
+ metadata.SetIsPaginatedPresentation(presContext->Type() !=
+ nsPresContext::eContext_Galley);
+
+ return metadata;
+}
+
+/*static*/
+Maybe<ScrollMetadata> nsLayoutUtils::GetRootMetadata(
+ nsDisplayListBuilder* aBuilder, WebRenderLayerManager* aLayerManager,
+ const std::function<bool(ViewID& aScrollId)>& aCallback) {
+ nsIFrame* frame = aBuilder->RootReferenceFrame();
+ nsPresContext* presContext = frame->PresContext();
+ PresShell* presShell = presContext->PresShell();
+ Document* document = presShell->GetDocument();
+
+ // There is one case where we want the root container layer to have metrics.
+ // If the parent process is using XUL windows, there is no root scrollframe,
+ // and without explicitly creating metrics there will be no guaranteed
+ // top-level APZC.
+ bool addMetrics = XRE_IsParentProcess() && !presShell->GetRootScrollFrame();
+
+ // Add metrics if there are none in the layer tree with the id (create an id
+ // if there isn't one already) of the root scroll frame/root content.
+ bool ensureMetricsForRootId = nsLayoutUtils::AsyncPanZoomEnabled(frame) &&
+ aBuilder->IsPaintingToWindow() &&
+ !presContext->GetParentPresContext();
+
+ nsIContent* content = nullptr;
+ nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame();
+ if (rootScrollFrame) {
+ content = rootScrollFrame->GetContent();
+ } else {
+ // If there is no root scroll frame, pick the document element instead.
+ // The only case we don't want to do this is in non-APZ fennec, where
+ // we want the root xul document to get a null scroll id so that the root
+ // content document gets the first non-null scroll id.
+ content = document->GetDocumentElement();
+ }
+
+ if (ensureMetricsForRootId && content) {
+ ViewID scrollId = nsLayoutUtils::FindOrCreateIDFor(content);
+ if (aCallback(scrollId)) {
+ ensureMetricsForRootId = false;
+ }
+ }
+
+ if (addMetrics || ensureMetricsForRootId) {
+ bool isRootContent = presContext->IsRootContentDocumentCrossProcess();
+
+ nsSize scrollPortSize = frame->GetSize();
+ if (isRootContent && rootScrollFrame) {
+ nsIScrollableFrame* scrollableFrame =
+ rootScrollFrame->GetScrollTargetFrame();
+ scrollPortSize = scrollableFrame->GetScrollPortRect().Size();
+ }
+ return Some(nsLayoutUtils::ComputeScrollMetadata(
+ frame, rootScrollFrame, content, frame,
+ aBuilder->ToReferenceFrame(frame), aLayerManager,
+ ScrollableLayerGuid::NULL_SCROLL_ID, scrollPortSize, isRootContent));
+ }
+
+ return Nothing();
+}
+
+/* static */
+void nsLayoutUtils::TransformToAncestorAndCombineRegions(
+ const nsRegion& aRegion, nsIFrame* aFrame, const nsIFrame* aAncestorFrame,
+ nsRegion* aPreciseTargetDest, nsRegion* aImpreciseTargetDest,
+ Maybe<Matrix4x4Flagged>* aMatrixCache, const DisplayItemClip* aClip) {
+ if (aRegion.IsEmpty()) {
+ return;
+ }
+ bool isPrecise;
+ RegionBuilder<nsRegion> transformedRegion;
+ for (nsRegion::RectIterator it = aRegion.RectIter(); !it.Done(); it.Next()) {
+ nsRect transformed = TransformFrameRectToAncestor(
+ aFrame, it.Get(), aAncestorFrame, &isPrecise, aMatrixCache);
+ if (aClip) {
+ transformed = aClip->ApplyNonRoundedIntersection(transformed);
+ if (aClip->GetRoundedRectCount() > 0) {
+ isPrecise = false;
+ }
+ }
+ transformedRegion.OrWith(transformed);
+ }
+ nsRegion* dest = isPrecise ? aPreciseTargetDest : aImpreciseTargetDest;
+ dest->OrWith(transformedRegion.ToRegion());
+ // If the region becomes too complex this has a large performance impact.
+ // We limit its complexity here.
+ if (dest->GetNumRects() > 12) {
+ dest->SimplifyOutward(6);
+ if (isPrecise) {
+ aPreciseTargetDest->OrWith(*aImpreciseTargetDest);
+ *aImpreciseTargetDest = std::move(*aPreciseTargetDest);
+ aImpreciseTargetDest->SimplifyOutward(6);
+ *aPreciseTargetDest = nsRegion();
+ }
+ }
+}
+
+/* static */
+bool nsLayoutUtils::ShouldUseNoFramesSheet(Document* aDocument) {
+ bool allowSubframes = true;
+ nsIDocShell* docShell = aDocument->GetDocShell();
+ if (docShell) {
+ docShell->GetAllowSubframes(&allowSubframes);
+ }
+ return !allowSubframes;
+}
+
+/* static */
+void nsLayoutUtils::GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult) {
+ aResult.Truncate();
+ AppendFrameTextContent(aFrame, aResult);
+}
+
+/* static */
+void nsLayoutUtils::AppendFrameTextContent(nsIFrame* aFrame,
+ nsAString& aResult) {
+ if (aFrame->IsTextFrame()) {
+ auto* const textFrame = static_cast<nsTextFrame*>(aFrame);
+ const auto offset = AssertedCast<uint32_t>(textFrame->GetContentOffset());
+ const auto length = AssertedCast<uint32_t>(textFrame->GetContentLength());
+ textFrame->TextFragment()->AppendTo(aResult, offset, length);
+ } else {
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ AppendFrameTextContent(child, aResult);
+ }
+ }
+}
+
+/* static */
+nsRect nsLayoutUtils::GetSelectionBoundingRect(const Selection* aSel) {
+ nsRect res;
+ // Bounding client rect may be empty after calling GetBoundingClientRect
+ // when range is collapsed. So we get caret's rect when range is
+ // collapsed.
+ if (aSel->IsCollapsed()) {
+ nsIFrame* frame = nsCaret::GetGeometry(aSel, &res);
+ if (frame) {
+ nsIFrame* relativeTo = GetContainingBlockForClientRect(frame);
+ res = TransformFrameRectToAncestor(frame, res, relativeTo);
+ }
+ } else {
+ RectAccumulator accumulator;
+ const uint32_t rangeCount = aSel->RangeCount();
+ for (const uint32_t idx : IntegerRange(rangeCount)) {
+ MOZ_ASSERT(aSel->RangeCount() == rangeCount);
+ nsRange* range = aSel->GetRangeAt(idx);
+ nsRange::CollectClientRectsAndText(
+ &accumulator, nullptr, range, range->GetStartContainer(),
+ range->StartOffset(), range->GetEndContainer(), range->EndOffset(),
+ true, false);
+ }
+ res = accumulator.mResultRect.IsEmpty() ? accumulator.mFirstRect
+ : accumulator.mResultRect;
+ }
+
+ return res;
+}
+
+/* static */
+nsBlockFrame* nsLayoutUtils::GetFloatContainingBlock(nsIFrame* aFrame) {
+ nsIFrame* ancestor = aFrame->GetParent();
+ while (ancestor && !ancestor->IsFloatContainingBlock()) {
+ ancestor = ancestor->GetParent();
+ }
+ MOZ_ASSERT(!ancestor || ancestor->IsBlockFrameOrSubclass(),
+ "Float containing block can only be block frame");
+ return static_cast<nsBlockFrame*>(ancestor);
+}
+
+// The implementations of this calculation are adapted from
+// Element::GetBoundingClientRect().
+/* static */
+CSSRect nsLayoutUtils::GetBoundingContentRect(
+ const nsIContent* aContent, const nsIScrollableFrame* aRootScrollFrame,
+ Maybe<CSSRect>* aOutNearestScrollClip) {
+ if (nsIFrame* frame = aContent->GetPrimaryFrame()) {
+ return GetBoundingFrameRect(frame, aRootScrollFrame, aOutNearestScrollClip);
+ }
+ return CSSRect();
+}
+
+/* static */
+CSSRect nsLayoutUtils::GetBoundingFrameRect(
+ nsIFrame* aFrame, const nsIScrollableFrame* aRootScrollFrame,
+ Maybe<CSSRect>* aOutNearestScrollClip) {
+ CSSRect result;
+ nsIFrame* relativeTo = aRootScrollFrame->GetScrolledFrame();
+ result = CSSRect::FromAppUnits(nsLayoutUtils::GetAllInFlowRectsUnion(
+ aFrame, relativeTo, nsLayoutUtils::RECTS_ACCOUNT_FOR_TRANSFORMS));
+
+ // If the element is contained in a scrollable frame that is not
+ // the root scroll frame, make sure to clip the result so that it is
+ // not larger than the containing scrollable frame's bounds.
+ nsIScrollableFrame* scrollFrame = nsLayoutUtils::GetNearestScrollableFrame(
+ aFrame, SCROLLABLE_INCLUDE_HIDDEN | SCROLLABLE_FIXEDPOS_FINDS_ROOT);
+ if (scrollFrame && scrollFrame != aRootScrollFrame) {
+ nsIFrame* subFrame = do_QueryFrame(scrollFrame);
+ MOZ_ASSERT(subFrame);
+ // Get the bounds of the scroll frame in the same coordinate space
+ // as |result|.
+ nsRect subFrameRect = subFrame->GetRectRelativeToSelf();
+ TransformResult res =
+ nsLayoutUtils::TransformRect(subFrame, relativeTo, subFrameRect);
+ MOZ_ASSERT(res == TRANSFORM_SUCCEEDED || res == NONINVERTIBLE_TRANSFORM);
+ if (res == TRANSFORM_SUCCEEDED) {
+ CSSRect subFrameRectCSS = CSSRect::FromAppUnits(subFrameRect);
+ if (aOutNearestScrollClip) {
+ *aOutNearestScrollClip = Some(subFrameRectCSS);
+ }
+
+ result = subFrameRectCSS.Intersect(result);
+ }
+ }
+ return result;
+}
+
+/* static */
+bool nsLayoutUtils::IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame) {
+ for (nsIFrame* f = aForFrame; f != aTopFrame; f = f->GetParent()) {
+ if (f->IsTransformed()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/*static*/
+CSSPoint nsLayoutUtils::GetCumulativeApzCallbackTransform(nsIFrame* aFrame) {
+ CSSPoint delta;
+ if (!aFrame) {
+ return delta;
+ }
+ nsIFrame* frame = aFrame;
+ nsCOMPtr<nsIContent> lastContent;
+ bool seenRcdRsf = false;
+
+ // Helper lambda to apply the callback transform for a single frame.
+ auto applyCallbackTransformForFrame = [&](nsIFrame* frame) {
+ if (frame) {
+ nsCOMPtr<nsIContent> content = frame->GetContent();
+ if (content && (content != lastContent)) {
+ void* property = content->GetProperty(nsGkAtoms::apzCallbackTransform);
+ if (property) {
+ delta += *static_cast<CSSPoint*>(property);
+ }
+ }
+ lastContent = content;
+ }
+ };
+
+ while (frame) {
+ // Apply the callback transform for the current frame.
+ applyCallbackTransformForFrame(frame);
+
+ // Keep track of whether we've encountered the RCD-RSF's content element.
+ nsPresContext* pc = frame->PresContext();
+ if (pc->IsRootContentDocumentCrossProcess()) {
+ if (PresShell* shell = pc->GetPresShell()) {
+ if (nsIFrame* rsf = shell->GetRootScrollFrame()) {
+ if (frame->GetContent() == rsf->GetContent()) {
+ seenRcdRsf = true;
+ }
+ }
+ }
+ }
+
+ // If we reach the RCD's viewport frame, but have not encountered
+ // the RCD-RSF, we were inside fixed content in the RCD.
+ // We still want to apply the RCD-RSF's callback transform because
+ // it contains the offset between the visual and layout viewports
+ // which applies to fixed content as well.
+ ViewportFrame* viewportFrame = do_QueryFrame(frame);
+ if (viewportFrame) {
+ if (pc->IsRootContentDocumentCrossProcess() && !seenRcdRsf) {
+ applyCallbackTransformForFrame(pc->PresShell()->GetRootScrollFrame());
+ }
+ }
+
+ // Proceed to the parent frame.
+ frame = GetCrossDocParentFrameInProcess(frame);
+ }
+ return delta;
+}
+
+static nsSize ComputeMaxSizeForPartialPrerender(nsIFrame* aFrame,
+ nsSize aMaxSize) {
+ Matrix4x4Flagged transform = nsLayoutUtils::GetTransformToAncestor(
+ RelativeTo{aFrame},
+ RelativeTo{nsLayoutUtils::GetDisplayRootFrame(aFrame)});
+
+ Matrix transform2D;
+ if (!transform.Is2D(&transform2D)) {
+ return aMaxSize;
+ }
+
+ gfx::Rect result(0, 0, aMaxSize.width, aMaxSize.height);
+ auto scale = transform2D.ScaleFactors();
+ if (scale.xScale != 0 && scale.yScale != 0) {
+ result.width /= scale.xScale;
+ result.height /= scale.yScale;
+ }
+
+ // Don't apply translate.
+ transform2D._31 = 0.0f;
+ transform2D._32 = 0.0f;
+
+ // Don't apply scale.
+ if (scale.xScale != 0 && scale.yScale != 0) {
+ transform2D._11 /= scale.xScale;
+ transform2D._12 /= scale.xScale;
+ transform2D._21 /= scale.yScale;
+ transform2D._22 /= scale.yScale;
+ }
+
+ // Theoretically we should use transform2D.Inverse() here but in this case
+ // |transform2D| is a pure rotation matrix, no scaling, no translate at all,
+ // so that the result bound's width and height would be pretty much same
+ // as the one rotated by the inverse matrix.
+ result = transform2D.TransformBounds(result);
+ return nsSize(
+ result.width < (float)nscoord_MAX ? result.width : nscoord_MAX,
+ result.height < (float)nscoord_MAX ? result.height : nscoord_MAX);
+}
+
+/* static */
+nsRect nsLayoutUtils::ComputePartialPrerenderArea(
+ nsIFrame* aFrame, const nsRect& aDirtyRect, const nsRect& aOverflow,
+ const nsSize& aPrerenderSize) {
+ nsSize maxSizeForPartialPrerender =
+ ComputeMaxSizeForPartialPrerender(aFrame, aPrerenderSize);
+ // Simple calculation for now: center the pre-render area on the dirty rect,
+ // and clamp to the overflow area. Later we can do more advanced things like
+ // redistributing from one axis to another, or from one side to another.
+ nscoord xExcess =
+ std::max(maxSizeForPartialPrerender.width - aDirtyRect.width, 0);
+ nscoord yExcess =
+ std::max(maxSizeForPartialPrerender.height - aDirtyRect.height, 0);
+ nsRect result = aDirtyRect;
+ result.Inflate(xExcess / 2, yExcess / 2);
+ return result.MoveInsideAndClamp(aOverflow);
+}
+
+static bool LineHasNonEmptyContentWorker(nsIFrame* aFrame) {
+ // Look for non-empty frames, but ignore inline and br frames.
+ // For inline frames, descend into the children, if any.
+ if (aFrame->IsInlineFrame()) {
+ for (nsIFrame* child : aFrame->PrincipalChildList()) {
+ if (LineHasNonEmptyContentWorker(child)) {
+ return true;
+ }
+ }
+ } else {
+ if (!aFrame->IsBrFrame() && !aFrame->IsEmpty()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+static bool LineHasNonEmptyContent(nsLineBox* aLine) {
+ int32_t count = aLine->GetChildCount();
+ for (nsIFrame* frame = aLine->mFirstChild; count > 0;
+ --count, frame = frame->GetNextSibling()) {
+ if (LineHasNonEmptyContentWorker(frame)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* static */
+bool nsLayoutUtils::IsInvisibleBreak(nsINode* aNode,
+ nsIFrame** aNextLineFrame) {
+ if (aNextLineFrame) {
+ *aNextLineFrame = nullptr;
+ }
+
+ if (!aNode->IsElement() || !aNode->IsEditable()) {
+ return false;
+ }
+ nsIFrame* frame = aNode->AsElement()->GetPrimaryFrame();
+ if (!frame || !frame->IsBrFrame()) {
+ return false;
+ }
+
+ nsContainerFrame* f = frame->GetParent();
+ while (f && f->IsLineParticipant()) {
+ f = f->GetParent();
+ }
+ nsBlockFrame* blockAncestor = do_QueryFrame(f);
+ if (!blockAncestor) {
+ // The container frame doesn't support line breaking.
+ return false;
+ }
+
+ bool valid = false;
+ nsBlockInFlowLineIterator iter(blockAncestor, frame, &valid);
+ if (!valid) {
+ return false;
+ }
+
+ bool lineNonEmpty = LineHasNonEmptyContent(iter.GetLine());
+ if (!lineNonEmpty) {
+ return false;
+ }
+
+ while (iter.Next()) {
+ auto currentLine = iter.GetLine();
+ // Completely skip empty lines.
+ if (!currentLine->IsEmpty()) {
+ // If we come across an inline line, the BR has caused a visible line
+ // break.
+ if (currentLine->IsInline()) {
+ if (aNextLineFrame) {
+ *aNextLineFrame = currentLine->mFirstChild;
+ }
+ return false;
+ }
+ break;
+ }
+ }
+
+ return lineNonEmpty;
+}
+
+/* static */
+nsRect nsLayoutUtils::ComputeSVGOriginBox(SVGViewportElement* aElement) {
+ if (!aElement) {
+ return {};
+ }
+
+ if (aElement->HasViewBox()) {
+ // Return the "origin box", which is defined as a rect positioned at the
+ // origin, but with the width and height given by the viewBox attribute
+ //
+ // https://drafts.csswg.org/css-box-3/#valdef-box-view-box
+ //
+ // For more discussion see
+ // https://github.com/web-platform-tests/interop/issues/509
+ const SVGViewBox& value = aElement->GetAnimatedViewBox()->GetAnimValue();
+ return nsRect(0, 0, nsPresContext::CSSPixelsToAppUnits(value.width),
+ nsPresContext::CSSPixelsToAppUnits(value.height));
+ }
+
+ // No viewBox is specified, uses the nearest SVG viewport as reference
+ // box.
+ auto viewportSize = aElement->GetViewportSize();
+ return nsRect(0, 0, nsPresContext::CSSPixelsToAppUnits(viewportSize.width),
+ nsPresContext::CSSPixelsToAppUnits(viewportSize.height));
+}
+
+/* static */
+nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame,
+ StyleGeometryBox aGeometryBox) {
+ MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
+ nsRect r;
+
+ switch (aGeometryBox) {
+ case StyleGeometryBox::StrokeBox: {
+ // XXX Bug 1299876
+ // The size of stroke-box is not correct if this graphic element has
+ // specific stroke-linejoin or stroke-linecap.
+ gfxRect bbox =
+ SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry |
+ SVGUtils::eBBoxIncludeStroke);
+ r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
+ break;
+ }
+ case StyleGeometryBox::ViewBox: {
+ SVGViewportElement* viewportElement =
+ SVGElement::FromNode(aFrame->GetContent())->GetCtx();
+ if (!viewportElement) {
+ // We should not render without a viewport so return an empty rect.
+ break;
+ }
+ r = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
+ break;
+ }
+ case StyleGeometryBox::FillBox: {
+ gfxRect bbox =
+ SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
+ r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
+ break;
+ }
+ default: {
+ MOZ_ASSERT_UNREACHABLE("unsupported SVG box");
+ break;
+ }
+ }
+
+ return r;
+}
+
+/* static */
+nsRect nsLayoutUtils::ComputeHTMLReferenceRect(const nsIFrame* aFrame,
+ StyleGeometryBox aGeometryBox) {
+ nsRect r;
+
+ switch (aGeometryBox) {
+ case StyleGeometryBox::ContentBox:
+ r = aFrame->GetContentRectRelativeToSelf();
+ break;
+ case StyleGeometryBox::PaddingBox:
+ r = aFrame->GetPaddingRectRelativeToSelf();
+ break;
+ case StyleGeometryBox::MarginBox:
+ r = aFrame->GetMarginRectRelativeToSelf();
+ break;
+ case StyleGeometryBox::BorderBox:
+ r = aFrame->GetRectRelativeToSelf();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("unsupported CSS box");
+ break;
+ }
+
+ return r;
+}
+
+static StyleGeometryBox ShapeBoxToGeometryBox(const StyleShapeBox& aBox) {
+ switch (aBox) {
+ case StyleShapeBox::BorderBox:
+ return StyleGeometryBox::BorderBox;
+ case StyleShapeBox::ContentBox:
+ return StyleGeometryBox::ContentBox;
+ case StyleShapeBox::MarginBox:
+ return StyleGeometryBox::MarginBox;
+ case StyleShapeBox::PaddingBox:
+ return StyleGeometryBox::PaddingBox;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
+ return StyleGeometryBox::MarginBox;
+}
+
+static StyleGeometryBox ClipPathBoxToGeometryBox(
+ const StyleShapeGeometryBox& aBox) {
+ using Tag = StyleShapeGeometryBox::Tag;
+ switch (aBox.tag) {
+ case Tag::ShapeBox:
+ return ShapeBoxToGeometryBox(aBox.AsShapeBox());
+ case Tag::ElementDependent:
+ return StyleGeometryBox::NoBox;
+ case Tag::FillBox:
+ return StyleGeometryBox::FillBox;
+ case Tag::StrokeBox:
+ return StyleGeometryBox::StrokeBox;
+ case Tag::ViewBox:
+ return StyleGeometryBox::ViewBox;
+ }
+ MOZ_ASSERT_UNREACHABLE("Unknown shape box type");
+ return StyleGeometryBox::NoBox;
+}
+
+// The mapping is from
+// https://drafts.fxtf.org/css-masking-1/#typedef-geometry-box
+/* static */
+nsRect nsLayoutUtils::ComputeClipPathGeometryBox(
+ nsIFrame* aFrame, const StyleShapeGeometryBox& aBox) {
+ StyleGeometryBox box = ClipPathBoxToGeometryBox(aBox);
+
+ if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
+ // For SVG elements without associated CSS layout box, the used value for
+ // content-box and padding-box is fill-box and for border-box and margin-box
+ // is stroke-box.
+ switch (box) {
+ case StyleGeometryBox::ContentBox:
+ case StyleGeometryBox::PaddingBox:
+ case StyleGeometryBox::FillBox:
+ return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::FillBox);
+ case StyleGeometryBox::NoBox:
+ case StyleGeometryBox::BorderBox:
+ case StyleGeometryBox::MarginBox:
+ case StyleGeometryBox::StrokeBox:
+ return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::StrokeBox);
+ case StyleGeometryBox::ViewBox:
+ return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::ViewBox);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown clip-path geometry box");
+ // Use default, border-box (as stroke-box in SVG layout).
+ return ComputeSVGReferenceRect(aFrame, StyleGeometryBox::StrokeBox);
+ }
+ }
+
+ // For elements with associated CSS layout box, the used value for fill-box is
+ // content-box and for stroke-box and view-box is border-box.
+ switch (box) {
+ case StyleGeometryBox::FillBox:
+ case StyleGeometryBox::ContentBox:
+ return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::ContentBox);
+ case StyleGeometryBox::NoBox:
+ case StyleGeometryBox::StrokeBox:
+ case StyleGeometryBox::ViewBox:
+ case StyleGeometryBox::BorderBox:
+ return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::BorderBox);
+ case StyleGeometryBox::PaddingBox:
+ return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::PaddingBox);
+ case StyleGeometryBox::MarginBox:
+ return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::MarginBox);
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown clip-path geometry box");
+ // Use default, border-box.
+ return ComputeHTMLReferenceRect(aFrame, StyleGeometryBox::BorderBox);
+ }
+}
+
+/* static */
+nsPoint nsLayoutUtils::ComputeOffsetToUserSpace(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame) {
+ nsPoint offsetToBoundingBox =
+ aBuilder->ToReferenceFrame(aFrame) -
+ SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
+ if (!aFrame->IsSVGFrame()) {
+ // Snap the offset if the reference frame is not a SVG frame, since other
+ // frames will be snapped to pixel when rendering.
+ offsetToBoundingBox =
+ nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
+ offsetToBoundingBox.x),
+ aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
+ offsetToBoundingBox.y));
+ }
+
+ // During SVG painting, the offset computed here is applied to the gfxContext
+ // "ctx" used to paint the mask. After applying only "offsetToBoundingBox",
+ // "ctx" would have its origin at the top left corner of frame's bounding box
+ // (over all continuations).
+ // However, SVG painting needs the origin to be located at the origin of the
+ // SVG frame's "user space", i.e. the space in which, for example, the
+ // frame's BBox lives.
+ // SVG geometry frames and foreignObject frames apply their own offsets, so
+ // their position is relative to their user space. So for these frame types,
+ // if we want "ctx" to be in user space, we first need to subtract the
+ // frame's position so that SVG painting can later add it again and the
+ // frame is painted in the right place.
+ gfxPoint toUserSpaceGfx =
+ SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
+ nsPoint toUserSpace =
+ nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
+ nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
+
+ return (offsetToBoundingBox - toUserSpace);
+}
+
+/* static */
+already_AddRefed<nsFontMetrics> nsLayoutUtils::GetMetricsFor(
+ nsPresContext* aPresContext, bool aIsVertical,
+ const nsStyleFont* aStyleFont, Length aFontSize, bool aUseUserFontSet) {
+ nsFont font = aStyleFont->mFont;
+ font.size = aFontSize;
+ gfxFont::Orientation orientation =
+ aIsVertical ? nsFontMetrics::eVertical : nsFontMetrics::eHorizontal;
+ nsFontMetrics::Params params;
+ params.language = aStyleFont->mLanguage;
+ params.explicitLanguage = aStyleFont->mExplicitLanguage;
+ params.orientation = orientation;
+ params.userFontSet =
+ aUseUserFontSet ? aPresContext->GetUserFontSet() : nullptr;
+ params.textPerf = aPresContext->GetTextPerfMetrics();
+ params.featureValueLookup = aPresContext->GetFontFeatureValuesLookup();
+ return aPresContext->GetMetricsFor(font, params);
+}
+
+/* static */
+void nsLayoutUtils::ComputeSystemFont(nsFont* aSystemFont,
+ LookAndFeel::FontID aFontID,
+ const nsFont& aDefaultVariableFont,
+ const Document* aDocument) {
+ gfxFontStyle fontStyle;
+ nsAutoString systemFontName;
+ if (!LookAndFeel::GetFont(aFontID, systemFontName, fontStyle)) {
+ return;
+ }
+ systemFontName.Trim("\"'");
+ NS_ConvertUTF16toUTF8 nameu8(systemFontName);
+ Servo_FontFamily_ForSystemFont(&nameu8, &aSystemFont->family);
+ aSystemFont->style = fontStyle.style;
+ aSystemFont->family.is_system_font = fontStyle.systemFont;
+ aSystemFont->weight = fontStyle.weight;
+ aSystemFont->stretch = fontStyle.stretch;
+ aSystemFont->size = Length::FromPixels(fontStyle.size);
+
+ // aSystemFont->langGroup = fontStyle.langGroup;
+
+ switch (StyleFontSizeAdjust::Tag(fontStyle.sizeAdjustBasis)) {
+ case StyleFontSizeAdjust::Tag::None:
+ aSystemFont->sizeAdjust = StyleFontSizeAdjust::None();
+ break;
+ case StyleFontSizeAdjust::Tag::ExHeight:
+ aSystemFont->sizeAdjust =
+ StyleFontSizeAdjust::ExHeight(fontStyle.sizeAdjust);
+ break;
+ case StyleFontSizeAdjust::Tag::CapHeight:
+ aSystemFont->sizeAdjust =
+ StyleFontSizeAdjust::CapHeight(fontStyle.sizeAdjust);
+ break;
+ case StyleFontSizeAdjust::Tag::ChWidth:
+ aSystemFont->sizeAdjust =
+ StyleFontSizeAdjust::ChWidth(fontStyle.sizeAdjust);
+ break;
+ case StyleFontSizeAdjust::Tag::IcWidth:
+ aSystemFont->sizeAdjust =
+ StyleFontSizeAdjust::IcWidth(fontStyle.sizeAdjust);
+ break;
+ case StyleFontSizeAdjust::Tag::IcHeight:
+ aSystemFont->sizeAdjust =
+ StyleFontSizeAdjust::IcHeight(fontStyle.sizeAdjust);
+ break;
+ }
+
+ if (aFontID == LookAndFeel::FontID::MozField ||
+ aFontID == LookAndFeel::FontID::MozButton ||
+ aFontID == LookAndFeel::FontID::MozList) {
+ // For textfields, buttons and selects, we use whatever font is defined by
+ // the system. Which it appears (and the assumption is) it is always a
+ // proportional font. Then we always use 2 points smaller than what the
+ // browser has defined as the default proportional font.
+ //
+ // This matches historical Windows behavior and other browsers.
+ auto newSize =
+ aDefaultVariableFont.size.ToCSSPixels() - CSSPixel::FromPoints(2.0f);
+ aSystemFont->size = Length::FromPixels(std::max(float(newSize), 0.0f));
+ }
+}
+
+/* static */
+bool nsLayoutUtils::ShouldHandleMetaViewport(const Document* aDocument) {
+ auto metaViewportOverride = nsIDocShell::META_VIEWPORT_OVERRIDE_NONE;
+ if (aDocument) {
+ if (nsIDocShell* docShell = aDocument->GetDocShell()) {
+ metaViewportOverride = docShell->GetMetaViewportOverride();
+ }
+ }
+ switch (metaViewportOverride) {
+ case nsIDocShell::META_VIEWPORT_OVERRIDE_ENABLED:
+ return true;
+ case nsIDocShell::META_VIEWPORT_OVERRIDE_DISABLED:
+ return false;
+ default:
+ MOZ_ASSERT(metaViewportOverride ==
+ nsIDocShell::META_VIEWPORT_OVERRIDE_NONE);
+ // The META_VIEWPORT_OVERRIDE_NONE case means that there is no override
+ // and we rely solely on the StaticPrefs.
+ return StaticPrefs::dom_meta_viewport_enabled();
+ }
+}
+
+/* static */
+ComputedStyle* nsLayoutUtils::StyleForScrollbar(
+ const nsIFrame* aScrollbarPart) {
+ // Get the closest content node which is not an anonymous scrollbar
+ // part. It should be the originating element of the scrollbar part.
+ nsIContent* content = aScrollbarPart->GetContent();
+ // Note that the content may be a normal element with scrollbar part
+ // value specified for its -moz-appearance, so don't rely on it being
+ // a native anonymous. Also note that we have to check the node name
+ // because anonymous element like generated content may originate a
+ // scrollbar.
+ MOZ_ASSERT(content, "No content for the scrollbar part?");
+ while (content && content->IsInNativeAnonymousSubtree() &&
+ content->IsAnyOfXULElements(
+ nsGkAtoms::scrollbar, nsGkAtoms::scrollbarbutton,
+ nsGkAtoms::scrollcorner, nsGkAtoms::slider, nsGkAtoms::thumb)) {
+ content = content->GetParent();
+ }
+ MOZ_ASSERT(content, "Native anonymous element with no originating node?");
+ // Use the style from the primary frame of the content.
+ // Note: it is important to use the primary frame rather than an
+ // ancestor frame of the scrollbar part for the correct handling of
+ // viewport scrollbar. The content of the scroll frame of the viewport
+ // is the root element, but its style inherits from the viewport.
+ // Since we need to use the style of root element for the viewport
+ // scrollbar, we have to get the style from the primary frame.
+ if (nsIFrame* primaryFrame = content->GetPrimaryFrame()) {
+ return primaryFrame->Style();
+ }
+ // If the element doesn't have primary frame, get the computed style
+ // from the element directly. This can happen on viewport, because
+ // the scrollbar of viewport may be shown when the root element has
+ // > display: none; overflow: scroll;
+ MOZ_ASSERT(
+ content == aScrollbarPart->PresContext()->Document()->GetRootElement(),
+ "Root element is the only case for this fallback "
+ "path to be triggered");
+ RefPtr<ComputedStyle> style =
+ ServoStyleSet::ResolveServoStyle(*content->AsElement());
+ // Dropping the strong reference is fine because the style should be
+ // held strongly by the element.
+ return style.get();
+}
+
+enum class FramePosition : uint8_t {
+ Unknown,
+ InView,
+ OutOfView,
+};
+
+// NOTE: Returns a pair of Nothing() and `FramePosition::Unknown` if |aFrame|
+// is not in out-of-process or if we haven't received enough information from
+// APZ.
+static std::pair<Maybe<ScreenRect>, FramePosition> GetFrameVisibleRectOnScreen(
+ const nsIFrame* aFrame) {
+ // We actually want the in-process top prescontext here.
+ nsPresContext* topContextInProcess =
+ aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
+ if (!topContextInProcess) {
+ // We are in chrome process.
+ return std::make_pair(Nothing(), FramePosition::Unknown);
+ }
+
+ if (topContextInProcess->Document()->IsTopLevelContentDocument()) {
+ // We are in the top of content document.
+ return std::make_pair(Nothing(), FramePosition::Unknown);
+ }
+
+ nsIDocShell* docShell = topContextInProcess->GetDocShell();
+ BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
+ if (!browserChild) {
+ // We are not in out-of-process iframe.
+ return std::make_pair(Nothing(), FramePosition::Unknown);
+ }
+
+ if (!browserChild->GetEffectsInfo().IsVisible()) {
+ // There is no visible rect on this iframe at all.
+ return std::make_pair(Some(ScreenRect()), FramePosition::Unknown);
+ }
+
+ Maybe<ScreenRect> visibleRect =
+ browserChild->GetTopLevelViewportVisibleRectInBrowserCoords();
+ if (!visibleRect) {
+ // We are unsure if we haven't received the transformed rectangle of the
+ // iframe's visible area.
+ return std::make_pair(Nothing(), FramePosition::Unknown);
+ }
+
+ nsIFrame* rootFrame = topContextInProcess->PresShell()->GetRootFrame();
+ nsRect transformedToIFrame = nsLayoutUtils::TransformFrameRectToAncestor(
+ aFrame, aFrame->InkOverflowRectRelativeToSelf(), rootFrame);
+
+ LayoutDeviceRect rectInLayoutDevicePixel = LayoutDeviceRect::FromAppUnits(
+ transformedToIFrame, topContextInProcess->AppUnitsPerDevPixel());
+
+ ScreenRect transformedToRoot = ViewAs<ScreenPixel>(
+ browserChild->GetChildToParentConversionMatrix().TransformBounds(
+ rectInLayoutDevicePixel),
+ PixelCastJustification::ContentProcessIsLayerInUiProcess);
+
+ FramePosition position = FramePosition::Unknown;
+ // we need to check whether the transformed rect is outside the iframe
+ // visible rect or not because in some cases the rect size is (0x0), thus
+ // the intersection between the transformed rect and the iframe visible rect
+ // would also be (0x0), then we can't tell whether the given nsIFrame is
+ // inside the iframe visible rect or not by calling BaseRect::IsEmpty for the
+ // intersection.
+ if (transformedToRoot.x > visibleRect->XMost() ||
+ transformedToRoot.y > visibleRect->YMost() ||
+ visibleRect->x > transformedToRoot.XMost() ||
+ visibleRect->y > transformedToRoot.YMost()) {
+ position = FramePosition::OutOfView;
+ } else {
+ position = FramePosition::InView;
+ }
+
+ return std::make_pair(Some(visibleRect->Intersect(transformedToRoot)),
+ position);
+}
+
+// static
+bool nsLayoutUtils::FrameIsScrolledOutOfViewInCrossProcess(
+ const nsIFrame* aFrame) {
+ auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
+ if (visibleRect.isNothing()) {
+ return false;
+ }
+
+ return visibleRect->IsEmpty() && framePosition != FramePosition::InView;
+}
+
+// static
+bool nsLayoutUtils::FrameIsMostlyScrolledOutOfViewInCrossProcess(
+ const nsIFrame* aFrame, nscoord aMargin) {
+ auto [visibleRect, framePosition] = GetFrameVisibleRectOnScreen(aFrame);
+ (void)framePosition;
+ if (visibleRect.isNothing()) {
+ return false;
+ }
+
+ nsPresContext* topContextInProcess =
+ aFrame->PresContext()->GetInProcessRootContentDocumentPresContext();
+ MOZ_ASSERT(topContextInProcess);
+
+ nsIDocShell* docShell = topContextInProcess->GetDocShell();
+ BrowserChild* browserChild = BrowserChild::GetFrom(docShell);
+ MOZ_ASSERT(browserChild);
+
+ auto scale =
+ browserChild->GetChildToParentConversionMatrix().As2D().ScaleFactors();
+ const CSSCoord cssMargin = CSSPixel::FromAppUnits(aMargin);
+ ScreenSize margin =
+ CSSSize(cssMargin, cssMargin) * ViewAs<CSSToScreenScale2D>(scale);
+
+ return visibleRect->width < margin.width ||
+ visibleRect->height < margin.height;
+}
+
+// static
+nsSize nsLayoutUtils::ExpandHeightForViewportUnits(nsPresContext* aPresContext,
+ const nsSize& aSize) {
+ nsSize sizeForViewportUnits = aPresContext->GetSizeForViewportUnits();
+
+ // |aSize| might be the size expanded to the minimum-scale size whereas the
+ // size for viewport units is not scaled so that we need to expand the |aSize|
+ // height by multiplying by the ratio of the viewport units height to the
+ // visible area height.
+ float vhExpansionRatio = (float)sizeForViewportUnits.height /
+ aPresContext->GetVisibleArea().height;
+
+ MOZ_ASSERT(aSize.height <= NSCoordSaturatingNonnegativeMultiply(
+ aSize.height, vhExpansionRatio));
+ return nsSize(aSize.width, NSCoordSaturatingNonnegativeMultiply(
+ aSize.height, vhExpansionRatio));
+}
+
+template <typename SizeType>
+/* static */ SizeType ExpandHeightForDynamicToolbarImpl(
+ const nsPresContext* aPresContext, const SizeType& aSize) {
+ MOZ_ASSERT(aPresContext);
+
+ LayoutDeviceIntSize displaySize;
+ if (RefPtr<MobileViewportManager> MVM =
+ aPresContext->PresShell()->GetMobileViewportManager()) {
+ displaySize = MVM->DisplaySize();
+ } else if (!nsLayoutUtils::GetDocumentViewerSize(aPresContext, displaySize)) {
+ return aSize;
+ }
+
+ float toolbarHeightRatio =
+ mozilla::ScreenCoord(aPresContext->GetDynamicToolbarMaxHeight()) /
+ mozilla::ViewAs<mozilla::ScreenPixel>(
+ displaySize,
+ mozilla::PixelCastJustification::LayoutDeviceIsScreenForBounds)
+ .height;
+
+ SizeType expandedSize = aSize;
+ static_assert(std::is_same_v<nsSize, SizeType> ||
+ std::is_same_v<CSSSize, SizeType>);
+ if constexpr (std::is_same_v<nsSize, SizeType>) {
+ expandedSize.height =
+ NSCoordSaturatingAdd(aSize.height, aSize.height * toolbarHeightRatio);
+ } else if (std::is_same_v<CSSSize, SizeType>) {
+ expandedSize.height = aSize.height + aSize.height * toolbarHeightRatio;
+ }
+ return expandedSize;
+}
+
+CSSSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ const nsPresContext* aPresContext, const CSSSize& aSize) {
+ return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);
+}
+nsSize nsLayoutUtils::ExpandHeightForDynamicToolbar(
+ const nsPresContext* aPresContext, const nsSize& aSize) {
+ return ExpandHeightForDynamicToolbarImpl(aPresContext, aSize);
+}
diff --git a/layout/base/nsLayoutUtils.h b/layout/base/nsLayoutUtils.h
new file mode 100644
index 0000000000..3cd01dce9f
--- /dev/null
+++ b/layout/base/nsLayoutUtils.h
@@ -0,0 +1,3221 @@
+/* -*- 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 nsLayoutUtils_h__
+#define nsLayoutUtils_h__
+
+#include "LayoutConstants.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RelativeTo.h"
+#include "mozilla/StaticPrefs_nglayout.h"
+#include "mozilla/SurfaceFromElementResult.h"
+#include "mozilla/SVGImageContext.h"
+#include "mozilla/ToString.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/layers/ScrollableLayerGuid.h"
+#include "mozilla/gfx/2D.h"
+
+#include "gfxPoint.h"
+#include "nsBoundingMetrics.h"
+#include "nsCSSPropertyIDSet.h"
+#include "nsFrameList.h"
+#include "nsThreadUtils.h"
+#include "Units.h"
+#include "mozilla/layers/LayersTypes.h"
+#include <limits>
+#include <algorithm>
+// If you're thinking of adding a new include here, please try hard to not.
+// This header file gets included just about everywhere and adding headers here
+// can dramatically increase avoidable build activity. Try instead:
+// - using a forward declaration
+// - putting the include in the .cpp file, if it is only needed by the body
+// - putting your new functions in some other less-widely-used header
+
+class gfxContext;
+class gfxFontEntry;
+class imgIContainer;
+class nsFrameList;
+class nsPresContext;
+class nsIContent;
+class nsIPrincipal;
+class nsIWidget;
+class nsAtom;
+class nsIScrollableFrame;
+class nsRegion;
+enum nsChangeHint : uint32_t;
+class nsFontMetrics;
+class nsFontFaceList;
+class nsIImageLoadingContent;
+class nsBlockFrame;
+class nsContainerFrame;
+class nsView;
+class nsIFrame;
+class nsPIDOMWindowOuter;
+class imgIRequest;
+struct nsStyleFont;
+
+namespace mozilla {
+class nsDisplayItem;
+class nsDisplayList;
+class nsDisplayListBuilder;
+enum class nsDisplayListBuilderMode : uint8_t;
+class RetainedDisplayListBuilder;
+struct AspectRatio;
+class ComputedStyle;
+class DisplayPortUtils;
+class PresShell;
+enum class PseudoStyleType : uint8_t;
+class EventListenerManager;
+enum class LayoutFrameType : uint8_t;
+struct IntrinsicSize;
+class ReflowOutput;
+class WritingMode;
+class DisplayItemClip;
+class EffectSet;
+struct ActiveScrolledRoot;
+enum class ScrollOrigin : uint8_t;
+enum class StyleImageOrientation : uint8_t;
+enum class StyleSystemFont : uint8_t;
+enum class StyleScrollbarWidth : uint8_t;
+struct OverflowAreas;
+namespace dom {
+class CanvasRenderingContext2D;
+class DOMRectList;
+class Document;
+class Element;
+class Event;
+class HTMLImageElement;
+class HTMLCanvasElement;
+class HTMLVideoElement;
+class ImageBitmap;
+class InspectorFontFace;
+class OffscreenCanvas;
+class Selection;
+class VideoFrame;
+} // namespace dom
+namespace gfx {
+struct RectCornerRadii;
+enum class ShapedTextFlags : uint16_t;
+} // namespace gfx
+namespace image {
+class ImageIntRegion;
+struct Resolution;
+} // namespace image
+namespace layers {
+struct FrameMetrics;
+struct ScrollMetadata;
+class Image;
+class StackingContextHelper;
+class Layer;
+class WebRenderLayerManager;
+} // namespace layers
+namespace widget {
+enum class TransparencyMode : uint8_t;
+}
+} // namespace mozilla
+
+// Flags to customize the behavior of nsLayoutUtils::DrawString.
+enum class DrawStringFlags {
+ Default = 0x0,
+ ForceHorizontal = 0x1 // Forces the text to be drawn horizontally.
+};
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DrawStringFlags)
+
+namespace mozilla {
+
+class RectCallback {
+ public:
+ virtual void AddRect(const nsRect& aRect) = 0;
+};
+
+} // namespace mozilla
+
+/**
+ * nsLayoutUtils is a namespace class used for various helper
+ * functions that are useful in multiple places in layout. The goal
+ * is not to define multiple copies of the same static helper.
+ */
+class nsLayoutUtils {
+ typedef mozilla::AspectRatio AspectRatio;
+ typedef mozilla::ComputedStyle ComputedStyle;
+ typedef mozilla::LengthPercentage LengthPercentage;
+ typedef mozilla::LengthPercentageOrAuto LengthPercentageOrAuto;
+ typedef mozilla::dom::DOMRectList DOMRectList;
+ typedef mozilla::layers::StackingContextHelper StackingContextHelper;
+ typedef mozilla::IntrinsicSize IntrinsicSize;
+ typedef mozilla::RelativeTo RelativeTo;
+ typedef mozilla::ScrollOrigin ScrollOrigin;
+ typedef mozilla::ViewportType ViewportType;
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+ typedef mozilla::gfx::sRGBColor sRGBColor;
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::gfx::ExtendMode ExtendMode;
+ typedef mozilla::gfx::SamplingFilter SamplingFilter;
+ typedef mozilla::gfx::Float Float;
+ typedef mozilla::gfx::Point Point;
+ typedef mozilla::gfx::Rect Rect;
+ typedef mozilla::gfx::RectDouble RectDouble;
+ typedef mozilla::gfx::Size Size;
+ typedef mozilla::gfx::Matrix4x4 Matrix4x4;
+ typedef mozilla::gfx::Matrix4x4Flagged Matrix4x4Flagged;
+ typedef mozilla::gfx::MatrixScales MatrixScales;
+ typedef mozilla::gfx::MatrixScalesDouble MatrixScalesDouble;
+ typedef mozilla::gfx::RectCornerRadii RectCornerRadii;
+ typedef mozilla::gfx::StrokeOptions StrokeOptions;
+ typedef mozilla::image::ImgDrawResult ImgDrawResult;
+
+ using nsDisplayItem = mozilla::nsDisplayItem;
+ using nsDisplayList = mozilla::nsDisplayList;
+ using nsDisplayListBuilder = mozilla::nsDisplayListBuilder;
+ using nsDisplayListBuilderMode = mozilla::nsDisplayListBuilderMode;
+ using RetainedDisplayListBuilder = mozilla::RetainedDisplayListBuilder;
+
+ public:
+ typedef mozilla::layers::FrameMetrics FrameMetrics;
+ typedef mozilla::layers::ScrollMetadata ScrollMetadata;
+ typedef mozilla::layers::ScrollableLayerGuid::ViewID ViewID;
+ typedef mozilla::CSSPoint CSSPoint;
+ typedef mozilla::CSSSize CSSSize;
+ typedef mozilla::CSSIntSize CSSIntSize;
+ typedef mozilla::CSSRect CSSRect;
+ typedef mozilla::ScreenMargin ScreenMargin;
+ typedef mozilla::LayoutDeviceIntSize LayoutDeviceIntSize;
+ typedef mozilla::LayoutDeviceRect LayoutDeviceRect;
+ typedef mozilla::PresShell PresShell;
+ typedef mozilla::StyleGeometryBox StyleGeometryBox;
+ typedef mozilla::SVGImageContext SVGImageContext;
+ typedef mozilla::LogicalSize LogicalSize;
+
+ /**
+ * Finds previously assigned ViewID for the given content element, if any.
+ * Returns whether a ViewID was previously assigned.
+ */
+ static bool FindIDFor(const nsIContent* aContent, ViewID* aOutViewId);
+
+ /**
+ * Finds previously assigned or generates a unique ViewID for the given
+ * content element.
+ */
+ static ViewID FindOrCreateIDFor(nsIContent* aContent);
+
+ /**
+ * Find content for given ID.
+ */
+ static nsIContent* FindContentFor(ViewID aId);
+
+ /**
+ * Find the scrollable frame for a given content element.
+ */
+ static nsIScrollableFrame* FindScrollableFrameFor(nsIContent* aContent);
+
+ /**
+ * Find the scrollable frame for a given ID.
+ */
+ static nsIScrollableFrame* FindScrollableFrameFor(ViewID aId);
+
+ /**
+ * Helper for FindScrollableFrameFor(), also used in DisplayPortUtils.
+ * Most clients should use FindScrollableFrameFor().
+ */
+ static nsIFrame* GetScrollFrameFromContent(nsIContent* aContent);
+
+ /**
+ * Find the ID for a given scrollable frame.
+ */
+ static ViewID FindIDForScrollableFrame(nsIScrollableFrame* aScrollable);
+
+ /**
+ * Notify the scroll frame with the given scroll id that its scroll offset
+ * is being sent to APZ as part of a paint-skip transaction.
+ *
+ * Normally, this notification happens during painting, after calls to
+ * ComputeScrollMetadata(). During paint-skipping that code is skipped,
+ * but it's still important for the scroll frame to be notified for
+ * correctness of relative scroll updates, so the code that sends the
+ * empty paint-skip transaction needs to call this.
+ */
+ static void NotifyPaintSkipTransaction(ViewID aScrollId);
+
+ /**
+ * Use heuristics to figure out the child list that
+ * aChildFrame is currently in.
+ */
+ static mozilla::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
+
+ /**
+ * Returns the ::before pseudo-element for aContent, if any.
+ */
+ static mozilla::dom::Element* GetBeforePseudo(const nsIContent* aContent);
+
+ /**
+ * Returns the frame corresponding to the ::before pseudo-element for
+ * aContent, if any.
+ */
+ static nsIFrame* GetBeforeFrame(const nsIContent* aContent);
+
+ /**
+ * Returns the ::after pseudo-element for aContent, if any.
+ */
+ static mozilla::dom::Element* GetAfterPseudo(const nsIContent* aContent);
+
+ /**
+ * Returns the frame corresponding to the ::after pseudo-element for aContent,
+ * if any.
+ */
+ static nsIFrame* GetAfterFrame(const nsIContent* aContent);
+
+ /**
+ * Returns the ::marker pseudo-element for aContent, if any.
+ */
+ static mozilla::dom::Element* GetMarkerPseudo(const nsIContent* aContent);
+
+ /**
+ * Returns the frame corresponding to the ::marker pseudo-element for
+ * aContent, if any.
+ */
+ static nsIFrame* GetMarkerFrame(const nsIContent* aContent);
+
+#ifdef ACCESSIBILITY
+ /**
+ * Set aText to the spoken text for the given ::marker content (aContent)
+ * if it has a frame, or the empty string otherwise.
+ */
+ static void GetMarkerSpokenText(const nsIContent* aContent, nsAString& aText);
+#endif
+
+ /**
+ * Given a frame, search up the frame tree until we find an
+ * ancestor that (or the frame itself) is of type aFrameType, if any.
+ *
+ * @param aFrame the frame to start at
+ * @param aFrameType the frame type to look for
+ * @param aStopAt a frame to stop at after we checked it
+ * @return a frame of the given type or nullptr if no
+ * such ancestor exists
+ */
+ static nsIFrame* GetClosestFrameOfType(nsIFrame* aFrame,
+ mozilla::LayoutFrameType aFrameType,
+ nsIFrame* aStopAt = nullptr);
+
+ /**
+ * Given a frame, search up the frame tree until we find an
+ * ancestor that (or the frame itself) is a "Page" frame, if any.
+ *
+ * @param aFrame the frame to start at
+ * @return a frame of type mozilla::LayoutFrameType::Page or nullptr if no
+ * such ancestor exists
+ */
+ static nsIFrame* GetPageFrame(nsIFrame* aFrame);
+
+ /**
+ * Given a frame which is the primary frame for an element,
+ * return the frame that has the non-pseudoelement ComputedStyle for
+ * the content.
+ * This is aPrimaryFrame itself except for tableWrapper frames.
+ *
+ * Given a non-null input, this will return null if and only if its
+ * argument is a table wrapper frame that is mid-destruction (and its
+ * table frame has been destroyed).
+ */
+ static nsIFrame* GetStyleFrame(nsIFrame* aPrimaryFrame);
+ static const nsIFrame* GetStyleFrame(const nsIFrame* aPrimaryFrame);
+
+ /**
+ * Given a content node,
+ * return the frame that has the non-pseudoelement ComputedStyle for
+ * the content. May return null.
+ * This is aContent->GetPrimaryFrame() except for tableWrapper frames.
+ */
+ static nsIFrame* GetStyleFrame(const nsIContent* aContent);
+
+ /**
+ * Returns the placeholder size for when the scrollbar is unthemed.
+ */
+ static mozilla::CSSIntCoord UnthemedScrollbarSize(
+ mozilla::StyleScrollbarWidth);
+
+ /**
+ * The inverse of GetStyleFrame. Returns |aStyleFrame| unless it is an inner
+ * table frame, in which case the table wrapper frame is returned.
+ */
+ static nsIFrame* GetPrimaryFrameFromStyleFrame(nsIFrame* aStyleFrame);
+ static const nsIFrame* GetPrimaryFrameFromStyleFrame(
+ const nsIFrame* aStyleFrame);
+
+ /**
+ * Similar to nsIFrame::IsPrimaryFrame except that this will return true
+ * for the inner table frame rather than for its wrapper frame.
+ */
+ static bool IsPrimaryStyleFrame(const nsIFrame* aFrame);
+
+ /**
+ * CompareTreePosition determines whether aFrame1 comes before or
+ * after aFrame2 in a preorder traversal of the frame tree, where out
+ * of flow frames are treated as children of their placeholders. This is
+ * basically the same ordering as DoCompareTreePosition(nsIContent*) except
+ * that it handles anonymous content properly and there are subtleties with
+ * continuations.
+ *
+ * @param aCommonAncestor either null, or a common ancestor of
+ * aContent1 and aContent2. Actually this is
+ * only a hint; if it's not an ancestor of
+ * aContent1 or aContent2, this function will
+ * still work, but it will be slower than
+ * normal.
+ * @return < 0 if aContent1 is before aContent2
+ * > 0 if aContent1 is after aContent2,
+ * 0 otherwise (meaning they're the same, or they're in
+ * different frame trees)
+ */
+ static int32_t CompareTreePosition(nsIFrame* aFrame1, nsIFrame* aFrame2,
+ nsIFrame* aCommonAncestor = nullptr) {
+ return DoCompareTreePosition(aFrame1, aFrame2, aCommonAncestor);
+ }
+
+ static int32_t CompareTreePosition(nsIFrame* aFrame1, nsIFrame* aFrame2,
+ nsTArray<nsIFrame*>& aFrame2Ancestors,
+ nsIFrame* aCommonAncestor = nullptr) {
+ return DoCompareTreePosition(aFrame1, aFrame2, aFrame2Ancestors,
+ aCommonAncestor);
+ }
+
+ static nsIFrame* FillAncestors(nsIFrame* aFrame, nsIFrame* aStopAtAncestor,
+ nsTArray<nsIFrame*>* aAncestors);
+
+ static int32_t DoCompareTreePosition(nsIFrame* aFrame1, nsIFrame* aFrame2,
+ nsIFrame* aCommonAncestor);
+ static int32_t DoCompareTreePosition(nsIFrame* aFrame1, nsIFrame* aFrame2,
+ nsTArray<nsIFrame*>& aFrame2Ancestors,
+ nsIFrame* aCommonAncestor);
+
+ /**
+ * LastContinuationWithChild gets the last continuation in aFrame's chain
+ * that has a child, or the first continuation if the frame has no children.
+ */
+ static nsContainerFrame* LastContinuationWithChild(nsContainerFrame* aFrame);
+
+ /**
+ * GetLastSibling simply finds the last sibling of aFrame, or returns nullptr
+ * if aFrame is null.
+ */
+ static nsIFrame* GetLastSibling(nsIFrame* aFrame);
+
+ /**
+ * FindSiblingViewFor locates the child of aParentView that aFrame's
+ * view should be inserted 'above' (i.e., before in sibling view
+ * order). This is the first child view of aParentView whose
+ * corresponding content is before aFrame's content (view siblings
+ * are in reverse content order).
+ */
+ static nsView* FindSiblingViewFor(nsView* aParentView, nsIFrame* aFrame);
+
+ /**
+ * Get the parent of aFrame. If aFrame is the root frame for a document,
+ * and the document has a parent document in the same view hierarchy, then
+ * we try to return the subdocumentframe in the parent document.
+ * @param aCrossDocOffset [in/out] if non-null, then as we cross documents
+ * an extra offset may be required and it will be added to aCrossDocOffset.
+ * Be careful dealing with this extra offset as it is in app units of the
+ * parent document, which may have a different app units per dev pixel ratio
+ * than the child document.
+ * Note that, while this function crosses document boundaries, it (naturally)
+ * cannot cross process boundaries.
+ */
+ static nsIFrame* GetCrossDocParentFrameInProcess(
+ const nsIFrame* aFrame, nsPoint* aCrossDocOffset = nullptr);
+
+ /**
+ * Does the same thing as GetCrossDocParentFrameInProcess().
+ * The purpose of having two functions is to more easily track which call
+ * sites have been audited to consider out-of-process iframes (bug 1599913).
+ * Once all call sites have been audited, this function can be removed.
+ */
+ static nsIFrame* GetCrossDocParentFrame(const nsIFrame* aFrame,
+ nsPoint* aCrossDocOffset = nullptr);
+
+ /**
+ * IsProperAncestorFrame checks whether aAncestorFrame is an ancestor
+ * of aFrame and not equal to aFrame.
+ * @param aCommonAncestor nullptr, or a common ancestor of aFrame and
+ * aAncestorFrame. If non-null, this can bound the search and speed up
+ * the function
+ */
+ static bool IsProperAncestorFrame(const nsIFrame* aAncestorFrame,
+ const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * Like IsProperAncestorFrame, but looks across document boundaries.
+ *
+ * Just like IsAncestorFrameCrossDoc, except that it returns false when
+ * aFrame == aAncestorFrame.
+ * TODO: Once after we fixed bug 1715932, this function should be removed.
+ */
+ static bool IsProperAncestorFrameCrossDoc(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * Like IsProperAncestorFrame, but looks across document boundaries.
+ *
+ * Just like IsAncestorFrameCrossDoc, except that it returns false when
+ * aFrame == aAncestorFrame.
+ */
+ static bool IsProperAncestorFrameCrossDocInProcess(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * IsAncestorFrameCrossDoc checks whether aAncestorFrame is an ancestor
+ * of aFrame or equal to aFrame, looking across document boundaries.
+ * @param aCommonAncestor nullptr, or a common ancestor of aFrame and
+ * aAncestorFrame. If non-null, this can bound the search and speed up
+ * the function.
+ *
+ * Just like IsProperAncestorFrameCrossDoc, except that it returns true when
+ * aFrame == aAncestorFrame.
+ *
+ * TODO: Bug 1700245, all call sites of this function will be eventually
+ * replaced by IsAncestorFrameCrossDocInProcess.
+ */
+ static bool IsAncestorFrameCrossDoc(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ /**
+ * IsAncestorFrameCrossDocInProcess checks whether aAncestorFrame is an
+ * ancestor of aFrame or equal to aFrame, looking across document boundaries
+ * in the same process.
+ * @param aCommonAncestor nullptr, or a common ancestor of aFrame and
+ * aAncestorFrame. If non-null, this can bound the search and speed up
+ * the function.
+ *
+ * Just like IsProperAncestorFrameCrossDoc, except that it returns true when
+ * aFrame == aAncestorFrame.
+ *
+ * NOTE: This function doesn't return true even if |aAncestorFrame| and
+ * |aFrame| is in the same process but they are not directly connected, e.g.
+ * both |aAncestorFrame| and |aFrame| in A domain documents, but there's
+ * another an iframe document domain B, such as A1 -> B1 ->A2 document tree.
+ */
+ static bool IsAncestorFrameCrossDocInProcess(
+ const nsIFrame* aAncestorFrame, const nsIFrame* aFrame,
+ const nsIFrame* aCommonAncestor = nullptr);
+
+ static mozilla::SideBits GetSideBitsForFixedPositionContent(
+ const nsIFrame* aFixedPosFrame);
+
+ /**
+ * Get the scroll id for the root scrollframe of the presshell of the given
+ * prescontext. Returns NULL_SCROLL_ID if it couldn't be found.
+ */
+ static ViewID ScrollIdForRootScrollFrame(nsPresContext* aPresContext);
+
+ /**
+ * GetScrollableFrameFor returns the scrollable frame for a scrolled frame
+ */
+ static nsIScrollableFrame* GetScrollableFrameFor(
+ const nsIFrame* aScrolledFrame);
+
+ /**
+ * GetNearestScrollableFrameForDirection locates the first ancestor of
+ * aFrame (or aFrame itself) that is scrollable with overflow:scroll or
+ * overflow:auto in the given direction and where either the scrollbar for
+ * that direction is visible or the frame can be scrolled by some
+ * positive amount in that direction.
+ * The search extends across document boundaries.
+ *
+ * @param aFrame the frame to start with
+ * @param aDirection Whether it's for horizontal or vertical scrolling.
+ * @return the nearest scrollable frame or nullptr if not found
+ */
+ static nsIScrollableFrame* GetNearestScrollableFrameForDirection(
+ nsIFrame* aFrame, mozilla::layers::ScrollDirections aDirections);
+
+ enum {
+ /**
+ * If the SCROLLABLE_SAME_DOC flag is set, then we only walk the frame tree
+ * up to the root frame in the current document.
+ */
+ SCROLLABLE_SAME_DOC = 0x01,
+ /**
+ * If the SCROLLABLE_INCLUDE_HIDDEN flag is set then we allow
+ * overflow:hidden scrollframes to be returned as scrollable frames.
+ */
+ SCROLLABLE_INCLUDE_HIDDEN = 0x02,
+ /**
+ * If the SCROLLABLE_ONLY_ASYNC_SCROLLABLE flag is set, then we only
+ * want to match scrollable frames for which WantAsyncScroll() returns
+ * true.
+ */
+ SCROLLABLE_ONLY_ASYNC_SCROLLABLE = 0x04,
+ /**
+ * If the SCROLLABLE_ALWAYS_MATCH_ROOT flag is set, then we will always
+ * return the root scrollable frame for the root document (in the current
+ * process) if we encounter it, whether or not it is async scrollable or
+ * overflow: hidden.
+ */
+ SCROLLABLE_ALWAYS_MATCH_ROOT = 0x08,
+ /**
+ * If the SCROLLABLE_FIXEDPOS_FINDS_ROOT flag is set, then for fixed-pos
+ * frames return the root scrollable frame for that document.
+ */
+ SCROLLABLE_FIXEDPOS_FINDS_ROOT = 0x10,
+ /**
+ * If the SCROLLABLE_STOP_AT_PAGE flag is set, then we stop searching
+ * for scrollable ancestors when seeing a nsPageFrame. This can be used
+ * to avoid finding the viewport scroll frame in Print Preview (which
+ * would be undesirable as a 'position:sticky' container for content).
+ */
+ SCROLLABLE_STOP_AT_PAGE = 0x20,
+ };
+ /**
+ * GetNearestScrollableFrame locates the first ancestor of aFrame
+ * (or aFrame itself) that is scrollable with overflow:scroll or
+ * overflow:auto in some direction.
+ *
+ * @param aFrame the frame to start with
+ * @param aFlags if SCROLLABLE_SAME_DOC is set, do not search across
+ * document boundaries. If SCROLLABLE_INCLUDE_HIDDEN is set, include
+ * frames scrollable with overflow:hidden.
+ * @return the nearest scrollable frame or nullptr if not found
+ */
+ static nsIScrollableFrame* GetNearestScrollableFrame(nsIFrame* aFrame,
+ uint32_t aFlags = 0);
+
+ /**
+ * GetScrolledRect returns the range of allowable scroll offsets
+ * for aScrolledFrame, assuming the scrollable overflow area is
+ * aScrolledFrameOverflowArea and the scrollport size is aScrollPortSize.
+ */
+ static nsRect GetScrolledRect(nsIFrame* aScrolledFrame,
+ const nsRect& aScrolledFrameOverflowArea,
+ const nsSize& aScrollPortSize,
+ mozilla::StyleDirection);
+
+ /**
+ * HasPseudoStyle returns true if aContent (whose primary style
+ * context is aComputedStyle) has the aPseudoElement pseudo-style
+ * attached to it; returns false otherwise.
+ *
+ * @param aContent the content node we're looking at
+ * @param aComputedStyle aContent's ComputedStyle
+ * @param aPseudoElement the id of the pseudo style we care about
+ * @param aPresContext the presentation context
+ * @return whether aContent has aPseudoElement style attached to it
+ */
+ static bool HasPseudoStyle(nsIContent* aContent,
+ ComputedStyle* aComputedStyle,
+ mozilla::PseudoStyleType aPseudoElement,
+ nsPresContext* aPresContext);
+
+ /**
+ * If this frame is a placeholder for a float, then return the float,
+ * otherwise return nullptr. aPlaceholder must be a placeholder frame.
+ */
+ static nsIFrame* GetFloatFromPlaceholder(nsIFrame* aPlaceholder);
+
+ // Combine aOrigClearType with aNewClearType, but limit the clear types
+ // to StyleClear::Left, Right, Both.
+ static mozilla::StyleClear CombineClearType(
+ mozilla::StyleClear aOrigClearType, mozilla::StyleClear aNewClearType);
+
+ /**
+ * Get the coordinates of a given DOM mouse event, relative to a given
+ * frame. Works only for DOM events generated by WidgetGUIEvents.
+ * @param aDOMEvent the event
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetDOMEventCoordinatesRelativeTo(
+ mozilla::dom::Event* aDOMEvent, nsIFrame* aFrame);
+
+ /**
+ * Get the coordinates of a given native mouse event, relative to a given
+ * frame.
+ * @param aEvent the event
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(
+ const mozilla::WidgetEvent* aEvent, RelativeTo aFrame);
+
+ /**
+ * Get the coordinates of a given point relative to an event and a
+ * given frame.
+ * @param aEvent the event
+ * @param aPoint the point to get the coordinates relative to
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(
+ const mozilla::WidgetEvent* aEvent,
+ const mozilla::LayoutDeviceIntPoint& aPoint, RelativeTo aFrame);
+
+ /**
+ * Get the coordinates of a given point relative to a widget and a
+ * given frame.
+ * @param aWidget the event src widget
+ * @param aPoint the point to get the coordinates relative to
+ * @param aFrame the frame to make coordinates relative to
+ * @return the point, or (NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE) if
+ * for some reason the coordinates for the mouse are not known (e.g.,
+ * the event is not a GUI event).
+ */
+ static nsPoint GetEventCoordinatesRelativeTo(
+ nsIWidget* aWidget, const mozilla::LayoutDeviceIntPoint& aPoint,
+ RelativeTo aFrame);
+
+ /**
+ * Get the popup frame of a given native mouse event.
+ * @param aRootPresContext only check popups within aRootPresContext or a
+ * descendant
+ * @param aEvent the event.
+ * @return Null, if there is no popup frame at the point, otherwise,
+ * returns top-most popup frame at the point.
+ */
+ static nsIFrame* GetPopupFrameForEventCoordinates(
+ nsPresContext* aRootPresContext, const mozilla::WidgetEvent* aEvent);
+
+ /**
+ * Get the popup frame of a given point relative to a widget.
+ * @param aRootPresContext only check popups within aRootPresContext or a
+ * descendant
+ * @param aEvent the event.
+ * @return Null, if there is no popup frame at the point, otherwise,
+ * returns top-most popup frame at the point.
+ */
+ enum class GetPopupFrameForPointFlags : uint8_t {
+ OnlyReturnFramesWithWidgets = 0x1,
+ };
+ static nsIFrame* GetPopupFrameForPoint(
+ nsPresContext* aRootPresContext, nsIWidget* aWidget,
+ const mozilla::LayoutDeviceIntPoint& aPoint,
+ GetPopupFrameForPointFlags aFlags = GetPopupFrameForPointFlags(0));
+
+ /**
+ * Get container and offset if aEvent collapses Selection.
+ * @param aPresShell The PresShell handling aEvent.
+ * @param aEvent The event having coordinates where you want to
+ * collapse Selection.
+ * @param aContainer Returns the container node at the point.
+ * Set nullptr if you don't need this.
+ * @param aOffset Returns offset in the container node at the point.
+ * Set nullptr if you don't need this.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ static void GetContainerAndOffsetAtEvent(PresShell* aPresShell,
+ const mozilla::WidgetEvent* aEvent,
+ nsIContent** aContainer,
+ int32_t* aOffset);
+
+ /**
+ * Translate from widget coordinates to the view's coordinates
+ * @param aPresContext the PresContext for the view
+ * @param aWidget the widget
+ * @param aPt the point relative to the widget
+ * @param aView view to which returned coordinates are relative
+ * @return the point in the view's coordinates
+ */
+ static nsPoint TranslateWidgetToView(nsPresContext* aPresContext,
+ nsIWidget* aWidget,
+ const mozilla::LayoutDeviceIntPoint& aPt,
+ nsView* aView);
+
+ /**
+ * Translate from view coordinates to the widget's coordinates.
+ * @param aPresContext the PresContext for the view
+ * @param aView the view
+ * @param aPt the point relative to the view
+ * @param aViewportType whether the point is in visual or layout coordinates
+ * @param aWidget the widget to which returned coordinates are relative
+ * @return the point in the view's coordinates
+ */
+ static mozilla::LayoutDeviceIntPoint TranslateViewToWidget(
+ nsPresContext* aPresContext, nsView* aView, nsPoint aPt,
+ ViewportType aViewportType, nsIWidget* aWidget);
+
+ static mozilla::LayoutDeviceIntPoint WidgetToWidgetOffset(
+ nsIWidget* aFromWidget, nsIWidget* aToWidget);
+
+ enum class FrameForPointOption {
+ /**
+ * When set, paint suppression is ignored, so we'll return non-root page
+ * elements even if paint suppression is stopping them from painting.
+ */
+ IgnorePaintSuppression = 1,
+ /**
+ * When set, clipping due to the root scroll frame (and any other viewport-
+ * related clipping) is ignored.
+ */
+ IgnoreRootScrollFrame,
+ /**
+ * When set, return only content in the same document as aFrame.
+ */
+ IgnoreCrossDoc,
+ /**
+ * When set, return only content that is actually visible.
+ */
+ OnlyVisible,
+ };
+
+ struct FrameForPointOptions {
+ using Bits = mozilla::EnumSet<FrameForPointOption>;
+
+ Bits mBits;
+ // If mBits contains OnlyVisible, what is the opacity threshold which we
+ // consider "opaque enough" to clobber stuff underneath.
+ float mVisibleThreshold;
+
+ FrameForPointOptions(Bits aBits, float aVisibleThreshold)
+ : mBits(aBits), mVisibleThreshold(aVisibleThreshold){};
+
+ MOZ_IMPLICIT FrameForPointOptions(Bits aBits)
+ : FrameForPointOptions(aBits, 1.0f) {}
+
+ FrameForPointOptions() : FrameForPointOptions(Bits()){};
+ };
+
+ /**
+ * Given aFrame, the root frame of a stacking context, find its descendant
+ * frame under the point aPt that receives a mouse event at that location,
+ * or nullptr if there is no such frame.
+ * @param aPt the point, relative to the frame origin, in either visual
+ * or layout coordinates depending on aRelativeTo.mViewportType
+ */
+ static nsIFrame* GetFrameForPoint(RelativeTo aRelativeTo, nsPoint aPt,
+ const FrameForPointOptions& = {});
+
+ /**
+ * Given aFrame, the root frame of a stacking context, find all descendant
+ * frames under the area of a rectangle that receives a mouse event,
+ * or nullptr if there is no such frame.
+ * @param aRect the rect, relative to the frame origin, in either visual
+ * or layout coordinates depending on aRelativeTo.mViewportType
+ * @param aOutFrames an array to add all the frames found
+ */
+ static nsresult GetFramesForArea(RelativeTo aRelativeTo, const nsRect& aRect,
+ nsTArray<nsIFrame*>& aOutFrames,
+ const FrameForPointOptions& = {});
+
+ /**
+ * Transform aRect relative to aFrame up to the coordinate system of
+ * aAncestor. Computes the bounding-box of the true quadrilateral.
+ * Pass non-null aPreservesAxisAlignedRectangles and it will be set to true if
+ * we only need to use a 2d transform that PreservesAxisAlignedRectangles().
+ * The corner positions of aRect are treated as meaningful even if aRect is
+ * empty.
+ *
+ * |aMatrixCache| allows for optimizations in recomputing the same matrix over
+ * and over. The argument can be one of the following values:
+ *
+ * nullptr (the default) - No optimization; the transform matrix is computed
+ * on every call to this function.
+ *
+ * non-null pointer to an empty Maybe<Matrix4x4> - Upon return, the Maybe is
+ * filled with the transform matrix that was computed. This can then be passed
+ * in to subsequent calls with the same source and destination frames to avoid
+ * recomputing the matrix.
+ *
+ * non-null pointer to a non-empty Matrix4x4 - The provided matrix will be
+ * used as the transform matrix and applied to the rect.
+ */
+ static nsRect TransformFrameRectToAncestor(
+ const nsIFrame* aFrame, const nsRect& aRect, const nsIFrame* aAncestor,
+ bool* aPreservesAxisAlignedRectangles = nullptr,
+ mozilla::Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr,
+ bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false,
+ nsIFrame** aOutAncestor = nullptr) {
+ return TransformFrameRectToAncestor(
+ aFrame, aRect, RelativeTo{aAncestor}, aPreservesAxisAlignedRectangles,
+ aMatrixCache, aStopAtStackingContextAndDisplayPortAndOOFFrame,
+ aOutAncestor);
+ }
+ static nsRect TransformFrameRectToAncestor(
+ const nsIFrame* aFrame, const nsRect& aRect, RelativeTo aAncestor,
+ bool* aPreservesAxisAlignedRectangles = nullptr,
+ mozilla::Maybe<Matrix4x4Flagged>* aMatrixCache = nullptr,
+ bool aStopAtStackingContextAndDisplayPortAndOOFFrame = false,
+ nsIFrame** aOutAncestor = nullptr);
+
+ /**
+ * Gets the transform for aFrame relative to aAncestor. Pass null for
+ * aAncestor to go up to the root frame. Including nsIFrame::IN_CSS_UNITS
+ * flag in aFlags will return CSS pixels, by default it returns device
+ * pixels.
+ * More info can be found in nsIFrame::GetTransformMatrix.
+ *
+ * Some notes on the possible combinations of |aFrame.mViewportType| and
+ * |aAncestor.mViewportType|:
+ *
+ * | aFrame. | aAncestor. | Notes
+ * | mViewportType | mViewportType |
+ * ==========================================================================
+ * | Layout | Layout | Commonplace, when both source and target
+ * | | | are inside zoom boundary.
+ * | | |
+ * | | | Could also happen in non-e10s setups
+ * | | | when both source and target are outside
+ * | | | the zoom boundary and the code is
+ * | | | oblivious to the existence of a zoom
+ * | | | boundary.
+ * ==========================================================================
+ * | Layout | Visual | Commonplace, used when hit testing visual
+ * | | | coordinates (e.g. coming from user input
+ * | | | events). We expected to encounter a
+ * | | | zoomed content root during traversal and
+ * | | | apply a layout-to-visual transform.
+ * ==========================================================================
+ * | Visual | Layout | Should never happen, will assert.
+ * ==========================================================================
+ * | Visual | Visual | In e10s setups, should only happen if
+ * | | | aFrame and aAncestor are both the
+ * | | | RCD viewport frame.
+ * | | |
+ * | | | In non-e10s setups, could happen with
+ * | | | different frames if they are both
+ * | | | outside the zoom boundary.
+ * ==========================================================================
+ */
+ static Matrix4x4Flagged GetTransformToAncestor(
+ RelativeTo aFrame, RelativeTo aAncestor, uint32_t aFlags = 0,
+ nsIFrame** aOutAncestor = nullptr);
+
+ /**
+ * Gets the scale factors of the transform for aFrame relative to the root
+ * frame if this transform can be drawn 2D, or the identity scale factors
+ * otherwise.
+ */
+ static MatrixScales GetTransformToAncestorScale(const nsIFrame* aFrame);
+
+ /**
+ * Gets the scale factors of the transform for aFrame relative to the root
+ * frame if this transform is 2D, or the identity scale factors otherwise.
+ * If some frame on the path from aFrame to the display root frame may have an
+ * animated scale, returns the identity scale factors.
+ */
+ static MatrixScales GetTransformToAncestorScaleExcludingAnimated(
+ nsIFrame* aFrame);
+
+ /**
+ * Gets a scale that includes CSS transforms in this process as well as the
+ * transform to ancestor scale passed down from our direct ancestor process
+ * (which includes any enclosing CSS transforms and resolution). Note: this
+ * does not include any resolution in the current process (this is on purpose
+ * because that is what the transform to ancestor field on FrameMetrics needs,
+ * see its definition for explanation as to why). This is the transform to
+ * ancestor scale to set on FrameMetrics.
+ */
+ static mozilla::ParentLayerToScreenScale2D
+ GetTransformToAncestorScaleCrossProcessForFrameMetrics(
+ const nsIFrame* aFrame);
+
+ /**
+ * Find the nearest common ancestor frame for aFrame1 and aFrame2. The
+ * ancestor frame could be cross-doc.
+ */
+ static const nsIFrame* FindNearestCommonAncestorFrame(
+ const nsIFrame* aFrame1, const nsIFrame* aFrame2);
+
+ /**
+ * Find the nearest common ancestor frame for aFrame1 and aFrame2, assuming
+ * that they are within the same block.
+ *
+ * Returns null if they are not within the same block.
+ */
+ static const nsIFrame* FindNearestCommonAncestorFrameWithinBlock(
+ const nsTextFrame* aFrame1, const nsTextFrame* aFrame2);
+
+ /**
+ * Whether author-specified borders / backgrounds disable theming for a given
+ * appearance value.
+ */
+ static bool AuthorSpecifiedBorderBackgroundDisablesTheming(
+ mozilla::StyleAppearance);
+
+ /**
+ * Transforms a list of CSSPoints from aFromFrame to aToFrame, taking into
+ * account all relevant transformations on the frames up to (but excluding)
+ * their nearest common ancestor.
+ * If we encounter a transform that we need to invert but which is
+ * non-invertible, we return NONINVERTIBLE_TRANSFORM. If the frames have
+ * no common ancestor, we return NO_COMMON_ANCESTOR.
+ * If this returns TRANSFORM_SUCCEEDED, the points in aPoints are transformed
+ * in-place, otherwise they are untouched.
+ */
+ enum TransformResult {
+ TRANSFORM_SUCCEEDED,
+ NO_COMMON_ANCESTOR,
+ NONINVERTIBLE_TRANSFORM
+ };
+ static TransformResult TransformPoints(RelativeTo aFromFrame,
+ RelativeTo aToFrame,
+ uint32_t aPointCount,
+ CSSPoint* aPoints);
+
+ /**
+ * Same as above function, but transform points in app units and
+ * handle 1 point per call.
+ */
+ static TransformResult TransformPoint(RelativeTo aFromFrame,
+ RelativeTo aToFrame, nsPoint& aPoint);
+
+ /**
+ * Transforms a rect from aFromFrame to aToFrame. In app units.
+ * Returns the bounds of the actual rect if the transform requires rotation
+ * or anything complex like that.
+ */
+ static TransformResult TransformRect(const nsIFrame* aFromFrame,
+ const nsIFrame* aToFrame, nsRect& aRect);
+
+ /**
+ * Converts app units to pixels (with optional snapping) and appends as a
+ * translation to aTransform.
+ */
+ static void PostTranslate(Matrix4x4& aTransform, const nsPoint& aOrigin,
+ float aAppUnitsPerPixel, bool aRounded);
+
+ /*
+ * Whether the frame should snap to grid. This will end up being passed
+ * as the aRounded parameter in PostTranslate above. SVG frames should
+ * not have their translation rounded.
+ */
+ static bool ShouldSnapToGrid(const nsIFrame* aFrame);
+
+ /**
+ * Get the border-box of aElement's primary frame, transformed it to be
+ * relative to aFrame.
+ */
+ static nsRect GetRectRelativeToFrame(mozilla::dom::Element* aElement,
+ nsIFrame* aFrame);
+
+ /**
+ * Returns true if aRect with border inflation of size aInflateSize contains
+ * aPoint.
+ */
+ static bool ContainsPoint(const nsRect& aRect, const nsPoint& aPoint,
+ nscoord aInflateSize);
+
+ /**
+ * Clamp aRect relative to aFrame to the scroll frames boundary searching from
+ * aFrame.
+ */
+ static nsRect ClampRectToScrollFrames(nsIFrame* aFrame, const nsRect& aRect);
+
+ /**
+ * Given a point in the global coordinate space, returns that point expressed
+ * in the coordinate system of aFrame. This effectively inverts all
+ * transforms between this point and the root frame.
+ *
+ * @param aFromType Specifies whether |aPoint| is in layout or visual
+ * coordinates.
+ * @param aFrame The frame that acts as the coordinate space container.
+ * @param aPoint The point, in global layout or visual coordinates (as per
+ * |aFromType|, to get in the frame-local space.
+ * @return aPoint, expressed in aFrame's canonical coordinate space.
+ */
+ static nsPoint TransformRootPointToFrame(ViewportType aFromType,
+ RelativeTo aFrame,
+ const nsPoint& aPoint) {
+ return TransformAncestorPointToFrame(aFrame, aPoint,
+ RelativeTo{nullptr, aFromType});
+ }
+
+ /**
+ * Transform aPoint relative to aAncestor down to the coordinate system of
+ * aFrame.
+ */
+ static nsPoint TransformAncestorPointToFrame(RelativeTo aFrame,
+ const nsPoint& aPoint,
+ RelativeTo aAncestor);
+
+ /**
+ * Helper function that, given a rectangle and a matrix, returns the smallest
+ * rectangle containing the image of the source rectangle.
+ *
+ * @param aBounds The rectangle to transform.
+ * @param aMatrix The matrix to transform it with.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The smallest rect that contains the image of aBounds.
+ */
+ static nsRect MatrixTransformRect(const nsRect& aBounds,
+ const Matrix4x4& aMatrix, float aFactor);
+ static nsRect MatrixTransformRect(const nsRect& aBounds,
+ const Matrix4x4Flagged& aMatrix,
+ float aFactor);
+
+ /**
+ * Helper function that, given a point and a matrix, returns the image
+ * of that point under the matrix transform.
+ *
+ * @param aPoint The point to transform.
+ * @param aMatrix The matrix to transform it with.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The image of the point under the transform.
+ */
+ static nsPoint MatrixTransformPoint(const nsPoint& aPoint,
+ const Matrix4x4& aMatrix, float aFactor);
+
+ /**
+ * Given a graphics rectangle in graphics space, return a rectangle in
+ * app space that contains the graphics rectangle, rounding out as necessary.
+ *
+ * @param aRect The graphics rect to round outward.
+ * @param aFactor The number of app units per graphics unit.
+ * @return The smallest rectangle in app space that contains aRect.
+ */
+ template <typename T>
+ static nsRect RoundGfxRectToAppRect(const T& aRect, const float aFactor);
+
+ /**
+ * Returns a subrectangle of aContainedRect that is entirely inside the
+ * rounded rect. Complex cases are handled conservatively by returning a
+ * smaller rect than necessary.
+ */
+ static nsRegion RoundedRectIntersectRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aContainedRect);
+ static nsIntRegion RoundedRectIntersectIntRect(
+ const nsIntRect& aRoundedRect, const RectCornerRadii& aCornerRadii,
+ const nsIntRect& aContainedRect);
+
+ /**
+ * Return whether any part of aTestRect is inside of the rounded
+ * rectangle formed by aBounds and aRadii (which are indexed by the
+ * enum HalfCorner constants in gfx/2d/Types.h). This is precise.
+ */
+ static bool RoundedRectIntersectsRect(const nsRect& aRoundedRect,
+ const nscoord aRadii[8],
+ const nsRect& aTestRect);
+
+ enum class PaintFrameFlags : uint32_t {
+ InTransform = 0x01,
+ SyncDecodeImages = 0x02,
+ WidgetLayers = 0x04,
+ IgnoreSuppression = 0x08,
+ DocumentRelative = 0x10,
+ HideCaret = 0x20,
+ ToWindow = 0x40,
+ ExistingTransaction = 0x80,
+ ForWebRender = 0x100,
+ UseHighQualityScaling = 0x200,
+ ResetViewportScrolling = 0x400,
+ };
+
+ /**
+ * Given aFrame, the root frame of a stacking context, paint it and its
+ * descendants to aRenderingContext.
+ * @param aRenderingContext a rendering context translated so that (0,0)
+ * is the origin of aFrame; for best results, (0,0) should transform
+ * to pixel-aligned coordinates. This can be null, in which case
+ * aFrame must be a "display root" (root frame for a root document,
+ * or the root of a popup) with an associated widget and we draw using
+ * the layer manager for the frame's widget.
+ * @param aDirtyRegion the region that must be painted, in the coordinates
+ * of aFrame.
+ * @param aBackstop paint the dirty area with this color before drawing
+ * the actual content; pass NS_RGBA(0,0,0,0) to draw no background.
+ * @param aBuilderMode Passed through to the display-list builder.
+ * @param aFlags if PAINT_IN_TRANSFORM is set, then we assume
+ * this is inside a transform or SVG foreignObject. If
+ * PAINT_SYNC_DECODE_IMAGES is set, we force synchronous decode on all
+ * images. If PAINT_WIDGET_LAYERS is set, aFrame must be a display root,
+ * and we will use the frame's widget's layer manager to paint
+ * even if aRenderingContext is non-null. This is useful if you want
+ * to force rendering to use the widget's layer manager for testing
+ * or speed. PAINT_WIDGET_LAYERS must be set if aRenderingContext is null.
+ * If PAINT_DOCUMENT_RELATIVE is used, the visible region is interpreted
+ * as being relative to the document (normally it's relative to the CSS
+ * viewport) and the document is painted as if no scrolling has occured.
+ * Only considered if PresShell::IgnoringViewportScrolling is true.
+ * If ResetViewportScrolling is used, then the root scroll frame's scroll
+ * position is set to 0 during painting, so that position:fixed elements
+ * are drawn in their initial position.
+ * PAINT_TO_WINDOW sets painting to window to true on the display list
+ * builder even if we can't tell that we are painting to the window.
+ * If PAINT_EXISTING_TRANSACTION is set, then BeginTransaction() has already
+ * been called on aFrame's widget's layer manager and should not be
+ * called again.
+ * If PAINT_COMPRESSED is set, the FrameLayerBuilder should be set to
+ * compressed mode to avoid short cut optimizations.
+ *
+ * So there are three possible behaviours:
+ * 1) PAINT_WIDGET_LAYERS is set and aRenderingContext is null; we paint
+ * by calling BeginTransaction on the widget's layer manager.
+ * 2) PAINT_WIDGET_LAYERS is set and aRenderingContext is non-null; we
+ * paint by calling BeginTransactionWithTarget on the widget's layer
+ * manager.
+ * 3) PAINT_WIDGET_LAYERS is not set and aRenderingContext is non-null;
+ * we paint by construct a BasicLayerManager and calling
+ * BeginTransactionWithTarget on it. This is desirable if we're doing
+ * something like drawWindow in a mode where what gets rendered doesn't
+ * necessarily correspond to what's visible in the window; we don't
+ * want to mess up the widget's layer tree.
+ */
+ static void PaintFrame(gfxContext* aRenderingContext, nsIFrame* aFrame,
+ const nsRegion& aDirtyRegion, nscolor aBackstop,
+ nsDisplayListBuilderMode aBuilderMode,
+ PaintFrameFlags aFlags = PaintFrameFlags(0));
+
+ /**
+ * Uses a binary search for find where the cursor falls in the line of text
+ * It also keeps track of the part of the string that has already been
+ * measured so it doesn't have to keep measuring the same text over and over.
+ *
+ * @param "aBaseWidth" contains the width in twips of the portion
+ * of the text that has already been measured, and aBaseInx contains
+ * the index of the text that has already been measured.
+ *
+ * @param aTextWidth returns (in twips) the length of the text that falls
+ * before the cursor aIndex contains the index of the text where the cursor
+ * falls.
+ */
+ static bool BinarySearchForPosition(DrawTarget* aDrawTarget,
+ nsFontMetrics& aFontMetrics,
+ const char16_t* aText, int32_t aBaseWidth,
+ int32_t aBaseInx, int32_t aStartInx,
+ int32_t aEndInx, int32_t aCursorPos,
+ int32_t& aIndex, int32_t& aTextWidth);
+
+ class BoxCallback {
+ public:
+ BoxCallback() = default;
+ virtual void AddBox(nsIFrame* aFrame) = 0;
+ bool mIncludeCaptionBoxForTable = true;
+ // Whether we are in a continuation or ib-split-sibling of the target we're
+ // measuring. This is useful because if we know we're in the target subtree
+ // and measuring against it we can avoid finding the common ancestor.
+ bool mInTargetContinuation = false;
+ };
+ /**
+ * Collect all CSS boxes associated with aFrame and its
+ * continuations, "drilling down" through table wrapper frames and
+ * some anonymous blocks since they're not real CSS boxes.
+ * If aFrame is null, no boxes are returned.
+ * SVG frames return a single box, themselves.
+ */
+ static void GetAllInFlowBoxes(nsIFrame* aFrame, BoxCallback* aCallback);
+
+ /**
+ * Like GetAllInFlowBoxes, but doesn't include continuations.
+ */
+ static void AddBoxesForFrame(nsIFrame* aFrame, BoxCallback* aCallback);
+
+ /**
+ * Find the first frame descendant of aFrame (including aFrame) which is
+ * not an anonymous frame that getBoxQuads/getClientRects should ignore.
+ */
+ static nsIFrame* GetFirstNonAnonymousFrame(nsIFrame* aFrame);
+
+ struct RectAccumulator : public mozilla::RectCallback {
+ nsRect mResultRect;
+ nsRect mFirstRect;
+ bool mSeenFirstRect;
+
+ RectAccumulator();
+
+ virtual void AddRect(const nsRect& aRect) override;
+ };
+
+ struct RectListBuilder : public mozilla::RectCallback {
+ DOMRectList* mRectList;
+
+ explicit RectListBuilder(DOMRectList* aList);
+ virtual void AddRect(const nsRect& aRect) override;
+ };
+
+ static nsIFrame* GetContainingBlockForClientRect(nsIFrame* aFrame);
+
+ enum {
+ RECTS_ACCOUNT_FOR_TRANSFORMS = 0x01,
+ // Two bits for specifying which box type to use.
+ // With neither bit set (default), use the border box.
+ RECTS_USE_CONTENT_BOX = 0x02,
+ RECTS_USE_PADDING_BOX = 0x04,
+ RECTS_USE_MARGIN_BOX = 0x06, // both bits set
+ RECTS_WHICH_BOX_MASK = 0x06 // bitmask for these two bits
+ };
+ /**
+ * Collect all CSS boxes (content, padding, border, or margin) associated
+ * with aFrame and its continuations, "drilling down" through table wrapper
+ * frames and some anonymous blocks since they're not real CSS boxes.
+ * The boxes are positioned relative to aRelativeTo (taking scrolling
+ * into account) and passed to the callback in frame-tree order.
+ * If aFrame is null, no boxes are returned.
+ * For SVG frames, returns one rectangle, the bounding box.
+ * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
+ * the boxes into aRelativeTo coordinates, transforms (including CSS
+ * and SVG transforms) are taken into account.
+ * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
+ * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
+ * Otherwise (by default), the border box is used.
+ */
+ static void GetAllInFlowRects(nsIFrame* aFrame, const nsIFrame* aRelativeTo,
+ mozilla::RectCallback* aCallback,
+ uint32_t aFlags = 0);
+
+ static void GetAllInFlowRectsAndTexts(
+ nsIFrame* aFrame, const nsIFrame* aRelativeTo,
+ mozilla::RectCallback* aCallback,
+ mozilla::dom::Sequence<nsString>* aTextList, uint32_t aFlags = 0);
+
+ /**
+ * Computes the union of all rects returned by GetAllInFlowRects. If
+ * the union is empty, returns the first rect.
+ * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
+ * the boxes into aRelativeTo coordinates, transforms (including CSS
+ * and SVG transforms) are taken into account.
+ * If aFlags includes one of RECTS_USE_CONTENT_BOX, RECTS_USE_PADDING_BOX,
+ * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
+ * Otherwise (by default), the border box is used.
+ */
+ static nsRect GetAllInFlowRectsUnion(nsIFrame* aFrame,
+ const nsIFrame* aRelativeTo,
+ uint32_t aFlags = 0);
+
+ enum { EXCLUDE_BLUR_SHADOWS = 0x01 };
+ /**
+ * Takes a text-shadow array from the style properties of a given nsIFrame and
+ * computes the union of those shadows along with the given initial rect.
+ * If there are no shadows, the initial rect is returned.
+ */
+ static nsRect GetTextShadowRectsUnion(const nsRect& aTextAndDecorationsRect,
+ nsIFrame* aFrame, uint32_t aFlags = 0);
+
+ /**
+ * Computes the destination rect that a given replaced element should render
+ * into, based on its CSS 'object-fit' and 'object-position' properties.
+ *
+ * @param aConstraintRect The constraint rect that we have at our disposal,
+ * which would e.g. be exactly filled by the image
+ * if we had "object-fit: fill".
+ * @param aIntrinsicSize The replaced content's intrinsic size, as reported
+ * by nsIFrame::GetIntrinsicSize().
+ * @param aIntrinsicRatio The replaced content's intrinsic ratio, as reported
+ * by nsIFrame::GetIntrinsicRatio().
+ * @param aStylePos The nsStylePosition struct that contains the 'object-fit'
+ * and 'object-position' values that we should rely on.
+ * (This should usually be the nsStylePosition for the
+ * replaced element in question, but not always. For
+ * example, a <video>'s poster-image has a dedicated
+ * anonymous element & child-frame, but we should still use
+ * the <video>'s 'object-fit' and 'object-position' values.)
+ * @param aAnchorPoint [out] A point that should be pixel-aligned by functions
+ * like nsLayoutUtils::DrawImage. See documentation
+ * in nsCSSRendering.h for ComputeObjectAnchorPoint.
+ * @return The nsRect into which we should render the replaced content (using
+ * the same coordinate space as the passed-in aConstraintRect).
+ */
+ static nsRect ComputeObjectDestRect(const nsRect& aConstraintRect,
+ const IntrinsicSize& aIntrinsicSize,
+ const AspectRatio& aIntrinsicRatio,
+ const nsStylePosition* aStylePos,
+ nsPoint* aAnchorPoint = nullptr);
+
+ /**
+ * Get the font metrics corresponding to the frame's style data.
+ * @param aFrame the frame
+ * @param aSizeInflation number to multiply font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsForFrame(
+ const nsIFrame* aFrame, float aSizeInflation);
+
+ static already_AddRefed<nsFontMetrics> GetInflatedFontMetricsForFrame(
+ const nsIFrame* aFrame) {
+ return GetFontMetricsForFrame(aFrame, FontSizeInflationFor(aFrame));
+ }
+
+ /**
+ * Get the font metrics corresponding to the given style data.
+ * @param aComputedStyle the style data
+ * @param aSizeInflation number to multiply font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsForComputedStyle(
+ const ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
+ float aSizeInflation = 1.0f,
+ uint8_t aVariantWidth = NS_FONT_VARIANT_WIDTH_NORMAL);
+
+ /**
+ * Get the font metrics of emphasis marks corresponding to the given
+ * style data. The result is same as GetFontMetricsForComputedStyle
+ * except that the font size is scaled down to 50%.
+ * @param aComputedStyle the style data
+ * @param aInflation number to multiple font size by
+ */
+ static already_AddRefed<nsFontMetrics> GetFontMetricsOfEmphasisMarks(
+ ComputedStyle* aComputedStyle, nsPresContext* aPresContext,
+ float aInflation) {
+ return GetFontMetricsForComputedStyle(aComputedStyle, aPresContext,
+ aInflation * 0.5f);
+ }
+
+ /**
+ * Find the immediate child of aParent whose frame subtree contains
+ * aDescendantFrame. Returns null if aDescendantFrame is not a descendant
+ * of aParent.
+ */
+ static nsIFrame* FindChildContainingDescendant(nsIFrame* aParent,
+ nsIFrame* aDescendantFrame);
+
+ /**
+ * Find the nearest ancestor that's a block
+ */
+ static nsBlockFrame* FindNearestBlockAncestor(nsIFrame* aFrame);
+
+ /**
+ * Find the nearest ancestor that's not for generated content. Will return
+ * aFrame if aFrame is not for generated content.
+ */
+ static nsIFrame* GetNonGeneratedAncestor(nsIFrame* aFrame);
+
+ /*
+ * Whether the frame is an nsBlockFrame which is not a wrapper block.
+ */
+ static bool IsNonWrapperBlock(nsIFrame* aFrame);
+
+ /**
+ * If aFrame is an out of flow frame, return its placeholder, otherwise
+ * return its parent.
+ */
+ static nsIFrame* GetParentOrPlaceholderFor(const nsIFrame* aFrame);
+
+ /**
+ * If aFrame is an out of flow frame, return its placeholder, otherwise
+ * return its (possibly cross-doc) parent.
+ */
+ static nsIFrame* GetParentOrPlaceholderForCrossDoc(const nsIFrame* aFrame);
+
+ /**
+ * Returns the frame that would act as the parent of aFrame when
+ * descending through the frame tree in display list building.
+ * Usually the same as GetParentOrPlaceholderForCrossDoc, except
+ * that pushed floats are treated as children of their containing
+ * block.
+ */
+ static nsIFrame* GetDisplayListParent(nsIFrame* aFrame);
+
+ /**
+ * Get a frame's previous continuation, or, if it doesn't have one, its
+ * previous block-in-inline-split sibling.
+ */
+ static nsIFrame* GetPrevContinuationOrIBSplitSibling(const nsIFrame* aFrame);
+
+ /**
+ * Get a frame's next continuation, or, if it doesn't have one, its
+ * block-in-inline-split sibling.
+ */
+ static nsIFrame* GetNextContinuationOrIBSplitSibling(const nsIFrame* aFrame);
+
+ /**
+ * Get the first frame in the continuation-plus-ib-split-sibling chain
+ * containing aFrame.
+ */
+ static nsIFrame* FirstContinuationOrIBSplitSibling(const nsIFrame* aFrame);
+
+ /**
+ * Get the last frame in the continuation-plus-ib-split-sibling chain
+ * containing aFrame.
+ */
+ static nsIFrame* LastContinuationOrIBSplitSibling(const nsIFrame* aFrame);
+
+ /**
+ * Is FirstContinuationOrIBSplitSibling(aFrame) going to return
+ * aFrame?
+ */
+ static bool IsFirstContinuationOrIBSplitSibling(const nsIFrame* aFrame);
+
+ /**
+ * Check whether aFrame is a part of the scrollbar or scrollcorner of
+ * the root content.
+ * @param aFrame the checking frame.
+ * @return true if the frame is a part of the scrollbar or scrollcorner of
+ * the root content.
+ */
+ static bool IsViewportScrollbarFrame(nsIFrame* aFrame);
+
+ /**
+ * Get the contribution of aFrame to its containing block's intrinsic
+ * size for the given physical axis. This considers the child's intrinsic
+ * width, its 'width', 'min-width', and 'max-width' properties (or 'height'
+ * variations if that's what matches aAxis) and its padding, border and margin
+ * in the corresponding dimension.
+ * @param aPercentageBasis an optional percentage basis (in aFrame's WM).
+ * If the basis is indefinite in a given axis, pass a size with
+ * NS_UNCONSTRAINEDSIZE in that component.
+ * If you pass Nothing() a percentage basis will be calculated from aFrame's
+ * ancestors' computed size in the relevant axis, if needed.
+ * @param aMarginBoxMinSizeClamp make the result fit within this margin-box
+ * size by reducing the *content size* (flooring at zero). This is used for:
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ */
+ enum {
+ IGNORE_PADDING = 0x01,
+ BAIL_IF_REFLOW_NEEDED = 0x02, // returns NS_INTRINSIC_ISIZE_UNKNOWN if so
+ MIN_INTRINSIC_ISIZE = 0x04, // use min-width/height instead of width/height
+ };
+ static nscoord IntrinsicForAxis(
+ mozilla::PhysicalAxis aAxis, gfxContext* aRenderingContext,
+ nsIFrame* aFrame, mozilla::IntrinsicISizeType aType,
+ const mozilla::Maybe<LogicalSize>& aPercentageBasis = mozilla::Nothing(),
+ uint32_t aFlags = 0, nscoord aMarginBoxMinSizeClamp = NS_MAXSIZE);
+ /**
+ * Calls IntrinsicForAxis with aFrame's parent's inline physical axis.
+ */
+ static nscoord IntrinsicForContainer(gfxContext* aRenderingContext,
+ nsIFrame* aFrame,
+ mozilla::IntrinsicISizeType aType,
+ uint32_t aFlags = 0);
+
+ /**
+ * Get the definite size contribution of aFrame for the given physical axis.
+ * This considers the child's 'min-width' property (or 'min-height' if the
+ * given axis is vertical), and its padding, border, and margin in the
+ * corresponding dimension. If the 'min-' property is 'auto' (and 'overflow'
+ * is 'visible') and the corresponding 'width'/'height' is definite it returns
+ * the "specified size" for:
+ * https://drafts.csswg.org/css-grid/#min-size-auto
+ * Note that the "transferred size" is not handled here; use IntrinsicForAxis.
+ * Note that any percentage in 'width'/'height' makes it count as indefinite.
+ * If the 'min-' property is 'auto' and 'overflow' is not 'visible', then it
+ * calculates the result as if the 'min-' computed value is zero.
+ * Otherwise, return NS_UNCONSTRAINEDSIZE.
+ *
+ * @param aPercentageBasis the percentage basis (in aFrame's WM).
+ * Pass NS_UNCONSTRAINEDSIZE if the basis is indefinite in either/both axes.
+ * @note this behavior is specific to Grid/Flexbox (currently) so aFrame
+ * should be a grid/flex item.
+ */
+ static nscoord MinSizeContributionForAxis(mozilla::PhysicalAxis aAxis,
+ gfxContext* aRC, nsIFrame* aFrame,
+ mozilla::IntrinsicISizeType aType,
+ const LogicalSize& aPercentageBasis,
+ uint32_t aFlags = 0);
+
+ /*
+ * Convert LengthPercentage to nscoord when percentages depend on the
+ * containing block size.
+ * @param aPercentBasis The width or height of the containing block
+ * (whichever the client wants to use for resolving percentages).
+ */
+ static nscoord ComputeCBDependentValue(nscoord aPercentBasis,
+ const LengthPercentage& aCoord) {
+ NS_ASSERTION(
+ aPercentBasis != NS_UNCONSTRAINEDSIZE,
+ "have unconstrained width or height; this should only result from very "
+ "large sizes, not attempts at intrinsic size calculation");
+ return aCoord.Resolve(aPercentBasis);
+ }
+ static nscoord ComputeCBDependentValue(nscoord aPercentBasis,
+ const LengthPercentageOrAuto& aCoord) {
+ if (aCoord.IsAuto()) {
+ return 0;
+ }
+ return ComputeCBDependentValue(aPercentBasis, aCoord.AsLengthPercentage());
+ }
+
+ static nscoord ComputeBSizeDependentValue(nscoord aContainingBlockBSize,
+ const LengthPercentageOrAuto&);
+
+ static nscoord ComputeBSizeValue(nscoord aContainingBlockBSize,
+ nscoord aContentEdgeToBoxSizingBoxEdge,
+ const LengthPercentage& aCoord) {
+ MOZ_ASSERT(aContainingBlockBSize != nscoord_MAX || !aCoord.HasPercent(),
+ "caller must deal with %% of unconstrained block-size");
+
+ nscoord result = aCoord.Resolve(aContainingBlockBSize);
+ // Clamp calc(), and the subtraction for box-sizing.
+ return std::max(0, result - aContentEdgeToBoxSizingBoxEdge);
+ }
+
+ /**
+ * The "extremum length" values (see ExtremumLength) were originally aimed at
+ * inline-size (or width, as it was before logicalization). For now, we return
+ * true for those here, so that we don't call ComputeBSizeValue with value
+ * types that it doesn't understand. (See bug 1113216.)
+ *
+ * FIXME (bug 567039, bug 527285)
+ * This isn't correct for the 'fill' value or for the 'min-*' or 'max-*'
+ * properties, which need to be handled differently by the callers of
+ * IsAutoBSize().
+ */
+ template <typename SizeOrMaxSize>
+ static bool IsAutoBSize(const SizeOrMaxSize& aCoord, nscoord aCBBSize) {
+ return aCoord.BehavesLikeInitialValueOnBlockAxis() ||
+ (aCBBSize == nscoord_MAX && aCoord.HasPercent());
+ }
+
+ static bool IsPaddingZero(const LengthPercentage& aLength) {
+ // clamp negative calc() to 0
+ return aLength.Resolve(nscoord_MAX) <= 0 && aLength.Resolve(0) <= 0;
+ }
+
+ static bool IsMarginZero(const LengthPercentage& aLength) {
+ return aLength.Resolve(nscoord_MAX) == 0 && aLength.Resolve(0) == 0;
+ }
+
+ static void MarkDescendantsDirty(nsIFrame* aSubtreeRoot);
+
+ static void MarkIntrinsicISizesDirtyIfDependentOnBSize(nsIFrame* aFrame);
+
+ /*
+ * Calculate the used values for 'width' and 'height' when width
+ * and height are 'auto'. The tentWidth and tentHeight arguments should be
+ * the result of applying the rules for computing intrinsic sizes and ratios.
+ * as specified by CSS 2.1 sections 10.3.2 and 10.6.2
+ */
+ static nsSize ComputeAutoSizeWithIntrinsicDimensions(
+ nscoord minWidth, nscoord minHeight, nscoord maxWidth, nscoord maxHeight,
+ nscoord tentWidth, nscoord tentHeight);
+
+ // Implement nsIFrame::GetPrefISize in terms of nsIFrame::AddInlinePrefISize
+ static nscoord PrefISizeFromInline(nsIFrame* aFrame,
+ gfxContext* aRenderingContext);
+
+ // Implement nsIFrame::GetMinISize in terms of nsIFrame::AddInlineMinISize
+ static nscoord MinISizeFromInline(nsIFrame* aFrame,
+ gfxContext* aRenderingContext);
+
+ // Get a suitable foreground color for painting aColor for aFrame.
+ static nscolor DarkenColorIfNeeded(nsIFrame* aFrame, nscolor aColor);
+
+ // Get a suitable foreground color for painting aField for aFrame.
+ // Type of aFrame is made a template parameter because nsIFrame is not
+ // a complete type in the header. Type-safety is not harmed given that
+ // DarkenColorIfNeeded requires an nsIFrame pointer.
+ template <typename Frame, typename T, typename S>
+ static nscolor GetColor(Frame* aFrame, T S::*aField) {
+ nscolor color = aFrame->GetVisitedDependentColor(aField);
+ return DarkenColorIfNeeded(aFrame, color);
+ }
+
+ // Get a baseline y position in app units that is snapped to device pixels.
+ static gfxFloat GetSnappedBaselineY(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aY, nscoord aAscent);
+ // Ditto for an x position (for vertical text). Note that for vertical-rl
+ // writing mode, the ascent value should be negated by the caller.
+ static gfxFloat GetSnappedBaselineX(nsIFrame* aFrame, gfxContext* aContext,
+ nscoord aX, nscoord aAscent);
+
+ static nscoord AppUnitWidthOfString(char16_t aC, nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ return AppUnitWidthOfString(&aC, 1, aFontMetrics, aDrawTarget);
+ }
+ static nscoord AppUnitWidthOfString(mozilla::Span<const char16_t> aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget) {
+ return nsLayoutUtils::AppUnitWidthOfString(
+ aString.Elements(), aString.Length(), aFontMetrics, aDrawTarget);
+ }
+ static nscoord AppUnitWidthOfString(const char16_t* aString, uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget);
+ static nscoord AppUnitWidthOfStringBidi(const nsString& aString,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ gfxContext& aContext) {
+ return nsLayoutUtils::AppUnitWidthOfStringBidi(
+ aString.get(), aString.Length(), aFrame, aFontMetrics, aContext);
+ }
+ static nscoord AppUnitWidthOfStringBidi(const char16_t* aString,
+ uint32_t aLength,
+ const nsIFrame* aFrame,
+ nsFontMetrics& aFontMetrics,
+ gfxContext& aContext);
+
+ static bool StringWidthIsGreaterThan(const nsString& aString,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget, nscoord aWidth);
+
+ static nsBoundingMetrics AppUnitBoundsOfString(const char16_t* aString,
+ uint32_t aLength,
+ nsFontMetrics& aFontMetrics,
+ DrawTarget* aDrawTarget);
+
+ static void DrawString(const nsIFrame* aFrame, nsFontMetrics& aFontMetrics,
+ gfxContext* aContext, const char16_t* aString,
+ int32_t aLength, nsPoint aPoint,
+ ComputedStyle* aComputedStyle = nullptr,
+ DrawStringFlags aFlags = DrawStringFlags::Default);
+
+ static nsPoint GetBackgroundFirstTilePos(const nsPoint& aDest,
+ const nsPoint& aFill,
+ const nsSize& aRepeatSize);
+
+ /**
+ * Supports only LTR or RTL. Bidi (mixed direction) is not supported.
+ */
+ static void DrawUniDirString(const char16_t* aString, uint32_t aLength,
+ const nsPoint& aPoint,
+ nsFontMetrics& aFontMetrics,
+ gfxContext& aContext);
+
+ /**
+ * Helper function for drawing text-shadow. The callback's job
+ * is to draw whatever needs to be blurred onto the given context.
+ */
+ typedef void (*TextShadowCallback)(gfxContext* aCtx, nsPoint aShadowOffset,
+ const nscolor& aShadowColor, void* aData);
+
+ static void PaintTextShadow(const nsIFrame* aFrame, gfxContext* aContext,
+ const nsRect& aTextRect, const nsRect& aDirtyRect,
+ const nscolor& aForegroundColor,
+ TextShadowCallback aCallback,
+ void* aCallbackData);
+
+ /**
+ * Gets the baseline to vertically center text from a font within a
+ * line of specified height.
+ * aIsInverted: true if the text is inverted relative to the block
+ * direction, so that the block-dir "ascent" corresponds to font
+ * descent. (Applies to sideways text in vertical-lr mode.)
+ *
+ * Returns the baseline position relative to the top of the line.
+ */
+ static nscoord GetCenteredFontBaseline(nsFontMetrics* aFontMetrics,
+ nscoord aLineHeight, bool aIsInverted);
+
+ /**
+ * Derive a baseline of |aFrame| (measured from its top border edge)
+ * from its first in-flow line box (not descending into anything with
+ * 'overflow' not 'visible', potentially including aFrame itself).
+ *
+ * Returns true if a baseline was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ static bool GetFirstLineBaseline(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame, nscoord* aResult);
+
+ /**
+ * Just like GetFirstLineBaseline, except also returns the top and
+ * bottom of the line with the baseline.
+ *
+ * Returns true if a line was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ struct LinePosition {
+ nscoord mBStart{nscoord_MAX};
+ nscoord mBaseline{nscoord_MAX};
+ nscoord mBEnd{nscoord_MAX};
+
+ LinePosition operator+(nscoord aOffset) const {
+ LinePosition result;
+ result.mBStart = mBStart + aOffset;
+ result.mBaseline = mBaseline + aOffset;
+ result.mBEnd = mBEnd + aOffset;
+ return result;
+ }
+ };
+ static bool GetFirstLinePosition(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame,
+ LinePosition* aResult);
+
+ /**
+ * Derive a baseline of |aFrame| (measured from its top border edge)
+ * from its last in-flow line box (not descending into anything with
+ * 'overflow' not 'visible', potentially including aFrame itself).
+ *
+ * Returns true if a baseline was found (and fills in aResult).
+ * Otherwise returns false.
+ */
+ static bool GetLastLineBaseline(mozilla::WritingMode aWritingMode,
+ const nsIFrame* aFrame, nscoord* aResult);
+
+ /**
+ * Returns a block-dir coordinate relative to this frame's origin that
+ * represents the logical block-end of the frame or its visible content,
+ * whichever is further from the origin.
+ * Relative positioning is ignored and margins and glyph bounds are not
+ * considered.
+ * This value will be >= mRect.BSize() and <= overflowRect.BEnd() unless
+ * relative positioning is applied.
+ */
+ static nscoord CalculateContentBEnd(mozilla::WritingMode aWritingMode,
+ nsIFrame* aFrame);
+
+ /**
+ * Gets the closest frame (the frame passed in or one of its parents) that
+ * qualifies as a "layer"; used in DOM0 methods that depends upon that
+ * definition. This is the nearest frame that is either positioned or scrolled
+ * (the child of a scroll frame).
+ */
+ static nsIFrame* GetClosestLayer(nsIFrame* aFrame);
+
+ /**
+ * Gets the graphics sampling filter for the frame
+ */
+ static SamplingFilter GetSamplingFilterForFrame(nsIFrame* aFrame);
+
+ static inline void InitDashPattern(StrokeOptions& aStrokeOptions,
+ mozilla::StyleBorderStyle aBorderStyle) {
+ if (aBorderStyle == mozilla::StyleBorderStyle::Dotted) {
+ static Float dot[] = {1.f, 1.f};
+ aStrokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dot);
+ aStrokeOptions.mDashPattern = dot;
+ } else if (aBorderStyle == mozilla::StyleBorderStyle::Dashed) {
+ static Float dash[] = {5.f, 5.f};
+ aStrokeOptions.mDashLength = MOZ_ARRAY_LENGTH(dash);
+ aStrokeOptions.mDashPattern = dash;
+ } else {
+ aStrokeOptions.mDashLength = 0;
+ aStrokeOptions.mDashPattern = nullptr;
+ }
+ }
+
+ /**
+ * Convert an nsRect to a gfxRect.
+ */
+ static gfxRect RectToGfxRect(const nsRect& aRect,
+ int32_t aAppUnitsPerDevPixel);
+
+ static gfxPoint PointToGfxPoint(const nsPoint& aPoint,
+ int32_t aAppUnitsPerPixel) {
+ return gfxPoint(gfxFloat(aPoint.x) / aAppUnitsPerPixel,
+ gfxFloat(aPoint.y) / aAppUnitsPerPixel);
+ }
+
+ /* N.B. The only difference between variants of the Draw*Image
+ * functions below is the type of the aImage argument.
+ */
+
+ /**
+ * Draw a background image. The image's dimensions are as specified in aDest;
+ * the image itself is not consulted to determine a size.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ *
+ * @param aContext
+ * The context to draw to, already set up with an appropriate scale and
+ * transform for drawing in app units.
+ * @param aForFrame
+ * The nsIFrame that we're drawing this image for.
+ * @param aImage
+ * The image.
+ * @param aDest
+ * The position and scaled area where one copy of the image should be drawn.
+ * This area represents the image itself in its correct position as defined
+ * with the background-position css property.
+ * @param aFill
+ * The area to be filled with copies of the image.
+ * @param aRepeatSize
+ * The distance between the positions of two subsequent repeats of the image.
+ * Sizes larger than aDest.Size() create gaps between the images.
+ * @param aAnchor
+ * A point in aFill which we will ensure is pixel-aligned in the output.
+ * @param aDirty
+ * Pixels outside this area may be skipped.
+ * @param aImageFlags
+ * Image flags of the imgIContainer::FLAG_* variety.
+ * @param aExtendMode
+ * How to extend the image over the dest rect.
+ */
+ static ImgDrawResult DrawBackgroundImage(
+ gfxContext& aContext, nsIFrame* aForFrame, nsPresContext* aPresContext,
+ imgIContainer* aImage, SamplingFilter aSamplingFilter,
+ const nsRect& aDest, const nsRect& aFill, const nsSize& aRepeatSize,
+ const nsPoint& aAnchor, const nsRect& aDirty, uint32_t aImageFlags,
+ ExtendMode aExtendMode, float aOpacity);
+
+ /**
+ * Draw an image.
+ * See https://wiki.mozilla.org/Gecko:Image_Snapping_and_Rendering
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aComputedStyle The ComputedStyle of the nsIFrame (or
+ * pseudo-element) for which this image is being
+ * drawn.
+ * @param aImage The image.
+ * @param aDest Where one copy of the image should mapped to.
+ * @param aFill The area to be filled with copies of the
+ * image.
+ * @param aAnchor A point in aFill which we will ensure is
+ * pixel-aligned in the output.
+ * @param aDirty Pixels outside this area may be skipped.
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety
+ */
+ static ImgDrawResult DrawImage(gfxContext& aContext,
+ ComputedStyle* aComputedStyle,
+ nsPresContext* aPresContext,
+ imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter,
+ const nsRect& aDest, const nsRect& aFill,
+ const nsPoint& aAnchor, const nsRect& aDirty,
+ uint32_t aImageFlags, float aOpacity = 1.0);
+
+ /**
+ * Draw a whole image without scaling or tiling.
+ *
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aDest The top-left where the image should be drawn.
+ * @param aDirty If non-null, then pixels outside this area may
+ * be skipped.
+ * @param aSVGContext Optionally provides an SVGImageContext.
+ * Callers should pass an SVGImageContext with at
+ * least the viewport size set if aImage may be of
+ * type imgIContainer::TYPE_VECTOR, or pass
+ * Nothing() if it is of type
+ * imgIContainer::TYPE_RASTER (to save cycles
+ * constructing an SVGImageContext, since this
+ * argument will be ignored for raster images).
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_* variety
+ * @param aSourceArea If non-null, this area is extracted from
+ * the image and drawn at aDest. It's
+ * in appunits. For best results it should
+ * be aligned with image pixels.
+ */
+ static ImgDrawResult DrawSingleUnscaledImage(
+ gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
+ const SamplingFilter aSamplingFilter, const nsPoint& aDest,
+ const nsRect* aDirty, const mozilla::SVGImageContext& aSVGContext,
+ uint32_t aImageFlags, const nsRect* aSourceArea = nullptr);
+
+ /**
+ * Draw a whole image without tiling.
+ *
+ * @param aRenderingContext Where to draw the image, set up with an
+ * appropriate scale and transform for drawing in
+ * app units.
+ * @param aImage The image.
+ * @param aDest The area that the image should fill.
+ * @param aDirty Pixels outside this area may be skipped.
+ * @param aSVGContext Optionally provides an SVGImageContext.
+ * Callers should pass an SVGImageContext with at
+ * least the viewport size set if aImage may be of
+ * type imgIContainer::TYPE_VECTOR, or pass
+ * Nothing() if it is of type
+ * imgIContainer::TYPE_RASTER (to save cycles
+ * constructing an SVGImageContext, since this
+ * argument will be ignored for raster images).
+ * @param aImageFlags Image flags of the imgIContainer::FLAG_*
+ * variety.
+ * @param aAnchor If non-null, a point which we will ensure
+ * is pixel-aligned in the output.
+ */
+ static ImgDrawResult DrawSingleImage(
+ gfxContext& aContext, nsPresContext* aPresContext, imgIContainer* aImage,
+ SamplingFilter aSamplingFilter, const nsRect& aDest, const nsRect& aDirty,
+ const mozilla::SVGImageContext& aSVGContext, uint32_t aImageFlags,
+ const nsPoint* aAnchorPoint = nullptr);
+
+ /**
+ * Given an imgIContainer, this method attempts to obtain an intrinsic
+ * px-valued height & width for it. If the imgIContainer has a non-pixel
+ * value for either height or width, this method tries to generate a pixel
+ * value for that dimension using the intrinsic ratio (if available). The
+ * intrinsic ratio will be assigned to aIntrinsicRatio; if there's no
+ * intrinsic ratio then (0, 0) will be assigned.
+ *
+ * This method will always set aGotWidth and aGotHeight to indicate whether
+ * we were able to successfully obtain (or compute) a value for each
+ * dimension.
+ *
+ * NOTE: This method is similar to ComputeSizeWithIntrinsicDimensions. The
+ * difference is that this one is simpler and is suited to places where we
+ * have less information about the frame tree.
+ *
+ * @param aResolution The resolution specified by the author for the image, or
+ * its intrinsic resolution.
+ *
+ * This will affect the intrinsic size size of the image
+ * (so e.g., if resolution is 2, and the image is 100x100,
+ * the intrinsic size of the image will be 50x50).
+ */
+ static void ComputeSizeForDrawing(imgIContainer* aImage,
+ const mozilla::image::Resolution&,
+ CSSIntSize& aImageSize,
+ AspectRatio& aIntrinsicRatio,
+ bool& aGotWidth, bool& aGotHeight);
+
+ /**
+ * Given an imgIContainer, this method attempts to obtain an intrinsic
+ * px-valued height & width for it. If the imgIContainer has a non-pixel
+ * value for either height or width, this method tries to generate a pixel
+ * value for that dimension using the intrinsic ratio (if available). If,
+ * after trying all these methods, no value is available for one or both
+ * dimensions, the corresponding dimension of aFallbackSize is used instead.
+ */
+ static CSSIntSize ComputeSizeForDrawingWithFallback(
+ imgIContainer* aImage, const mozilla::image::Resolution&,
+ const nsSize& aFallbackSize);
+
+ /**
+ * Given the image container, frame, and dest rect, determine the best fitting
+ * size to decode the image at, and calculate any necessary SVG parameters.
+ */
+ static mozilla::gfx::IntSize ComputeImageContainerDrawingParameters(
+ imgIContainer* aImage, nsIFrame* aForFrame,
+ const LayoutDeviceRect& aDestRect, const LayoutDeviceRect& aFillRect,
+ const StackingContextHelper& aSc, uint32_t aFlags,
+ mozilla::SVGImageContext& aSVGContext,
+ mozilla::Maybe<mozilla::image::ImageIntRegion>& aRegion);
+
+ /**
+ * Given a source area of an image (in appunits) and a destination area
+ * that we want to map that source area too, computes the area that
+ * would be covered by the whole image. This is useful for passing to
+ * the aDest parameter of DrawImage, when we want to draw a subimage
+ * of an overall image.
+ */
+ static nsRect GetWholeImageDestination(const nsSize& aWholeImageSize,
+ const nsRect& aImageSourceArea,
+ const nsRect& aDestArea);
+
+ /**
+ * Given an image container and an orientation, returns an image container
+ * that contains the same image, reoriented appropriately. May return the
+ * original image container if no changes are needed.
+ *
+ * @param aContainer The image container to apply the orientation to.
+ * @param aOrientation The desired orientation.
+ */
+ static already_AddRefed<imgIContainer> OrientImage(
+ imgIContainer* aContainer,
+ const mozilla::StyleImageOrientation& aOrientation);
+
+ /**
+ * Given an image request, determine if the request uses CORS.
+ */
+ static bool ImageRequestUsesCORS(imgIRequest* aRequest);
+
+ /**
+ * Determine if any corner radius is of nonzero size
+ * @param aCorners the |BorderRadius| object to check
+ * @return true unless all the coordinates are 0%, 0 or null.
+ *
+ * A corner radius with one dimension zero and one nonzero is
+ * treated as a nonzero-radius corner, even though it will end up
+ * being rendered like a zero-radius corner. This is because such
+ * corners are not expected to appear outside of test cases, and it's
+ * simpler to implement the test this way.
+ */
+ static bool HasNonZeroCorner(const mozilla::BorderRadius& aCorners);
+
+ /**
+ * Determine if there is any corner radius on corners adjacent to the
+ * given side.
+ */
+ static bool HasNonZeroCornerOnSide(const mozilla::BorderRadius& aCorners,
+ mozilla::Side aSide);
+
+ /**
+ * Determine if a widget is likely to require transparency or translucency.
+ * @param aBackgroundFrame The frame that the background is set on. For
+ * <window>s, this will be the canvas frame.
+ * @param aCSSRootFrame The frame that holds CSS properties affecting
+ * the widget's transparency. For menupopups,
+ * aBackgroundFrame and aCSSRootFrame will be the
+ * same.
+ * @return a value suitable for passing to SetWindowTranslucency.
+ */
+ using TransparencyMode = mozilla::widget::TransparencyMode;
+ static TransparencyMode GetFrameTransparency(nsIFrame* aBackgroundFrame,
+ nsIFrame* aCSSRootFrame);
+
+ /**
+ * A frame is a popup if it has its own floating window. Menus, panels
+ * and combobox dropdowns are popups.
+ */
+ static bool IsPopup(const nsIFrame* aFrame);
+
+ /**
+ * Find the nearest "display root". This is the nearest enclosing
+ * popup frame or the root prescontext's root frame.
+ */
+ static nsIFrame* GetDisplayRootFrame(nsIFrame* aFrame);
+ static const nsIFrame* GetDisplayRootFrame(const nsIFrame* aFrame);
+
+ /**
+ * Get the reference frame that would be used when constructing a
+ * display item for this frame. Rather than using their own frame
+ * as a reference frame.)
+ *
+ * This duplicates some of the logic of GetDisplayRootFrame above and
+ * of nsDisplayListBuilder::FindReferenceFrameFor.
+ *
+ * If you have an nsDisplayListBuilder, you should get the reference
+ * frame from it instead of calling this.
+ */
+ static nsIFrame* GetReferenceFrame(nsIFrame* aFrame);
+
+ /**
+ * Get textrun construction flags determined by a given style; in particular
+ * some combination of:
+ * -- TEXT_DISABLE_OPTIONAL_LIGATURES if letter-spacing is in use
+ * -- TEXT_OPTIMIZE_SPEED if the text-rendering CSS property and font size
+ * and prefs indicate we should be optimizing for speed over quality
+ */
+ static mozilla::gfx::ShapedTextFlags GetTextRunFlagsForStyle(
+ const ComputedStyle*, nsPresContext*, const nsStyleFont*,
+ const nsStyleText*, nscoord aLetterSpacing);
+
+ /**
+ * Get orientation flags for textrun construction.
+ */
+ static mozilla::gfx::ShapedTextFlags GetTextRunOrientFlagsForStyle(
+ const ComputedStyle*);
+
+ /**
+ * Takes two rectangles whose origins must be the same, and computes
+ * the difference between their union and their intersection as two
+ * rectangles. (This difference is a superset of the difference
+ * between the two rectangles.)
+ */
+ static void GetRectDifferenceStrips(const nsRect& aR1, const nsRect& aR2,
+ nsRect* aHStrip, nsRect* aVStrip);
+
+ /**
+ * Get a device context that can be used to get up-to-date device
+ * dimensions for the given window. For some reason, this is more
+ * complicated than it ought to be in multi-monitor situations.
+ */
+ static nsDeviceContext* GetDeviceContextForScreenInfo(
+ nsPIDOMWindowOuter* aWindow);
+
+ /**
+ * Some frames with 'position: fixed' (nsStyleDisplay::mPosition ==
+ * StylePositionProperty::Fixed) are not really fixed positioned, since
+ * they're inside a transformed element or other element that establishes a
+ * fixed-pos containing block). This function says whether such an element is
+ * a real fixed-pos element.
+ */
+ static bool IsReallyFixedPos(const nsIFrame* aFrame);
+
+ /**
+ * This function says whether `aFrame` would really be a fixed positioned
+ * frame if the frame was created with StylePositionProperty::Fixed.
+ *
+ * It is effectively the same as IsReallyFixedPos, but without asserting the
+ * position value. Use it only when you know what you're doing, like when
+ * tearing down the frame tree (where styles may have changed due to
+ * ::first-line reparenting and rule changes at the same time).
+ */
+ static bool MayBeReallyFixedPos(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if |aFrame| is inside position:fixed subtree.
+ */
+ static bool IsInPositionFixedSubtree(const nsIFrame* aFrame);
+
+ /**
+ * Obtain a SourceSurface from the given DOM element, if possible.
+ * This obtains the most natural surface from the element; that
+ * is, the one that can be obtained with the fewest conversions.
+ *
+ * The flags below can modify the behaviour of this function. The
+ * result is returned as a SurfaceFromElementResult struct, also
+ * defined below.
+ *
+ * Currently, this will do:
+ * - HTML Canvas elements: will return the underlying canvas surface
+ * - HTML Video elements: will return the current video frame
+ * - Image elements: will return the image
+ *
+ * The above results are modified by the below flags (copying,
+ * forcing image surface, etc.).
+ */
+
+ enum {
+ /* Whether to extract the first frame (as opposed to the
+ current frame) in the case that the element is an image. */
+ SFE_WANT_FIRST_FRAME_IF_IMAGE = 1 << 0,
+ /* Whether we should skip colorspace/gamma conversion */
+ SFE_NO_COLORSPACE_CONVERSION = 1 << 1,
+ /* Caller handles SFER::mAlphaType = NonPremult */
+ SFE_ALLOW_NON_PREMULT = 1 << 2,
+ /* Whether we should skip getting a surface for vector images and
+ return a DirectDrawInfo containing an imgIContainer instead. */
+ SFE_NO_RASTERIZING_VECTORS = 1 << 3,
+ /* If image type is vector, the return surface size will same as
+ element size, not image's intrinsic size. */
+ SFE_USE_ELEMENT_SIZE_IF_VECTOR = 1 << 4,
+ /* Ensure that the returned surface has a size that matches the
+ * SurfaceFromElementResult::mSize. This is mostly a convenience thing so
+ * that callers who want this don't have to deal with it themselves.
+ * The surface might be different for, e.g., a EXIF-scaled raster image, if
+ * we don't rescale during decode. */
+ SFE_EXACT_SIZE_SURFACE = 1 << 6,
+ /* Use orientation from image */
+ SFE_ORIENTATION_FROM_IMAGE = 1 << 7,
+ /* Caller handles SFER::mCropRect.isSome() */
+ SFE_ALLOW_UNCROPPED_UNSCALED = 1 << 8,
+ };
+
+ // This function can be called on any thread.
+ static mozilla::SurfaceFromElementResult SurfaceFromOffscreenCanvas(
+ mozilla::dom::OffscreenCanvas* aOffscreenCanvas, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static mozilla::SurfaceFromElementResult SurfaceFromOffscreenCanvas(
+ mozilla::dom::OffscreenCanvas* aOffscreenCanvas,
+ uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromOffscreenCanvas(aOffscreenCanvas, aSurfaceFlags, target);
+ }
+ // This function can be called on any thread.
+ static mozilla::SurfaceFromElementResult SurfaceFromVideoFrame(
+ mozilla::dom::VideoFrame* aVideoFrame, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static mozilla::SurfaceFromElementResult SurfaceFromVideoFrame(
+ mozilla::dom::VideoFrame* aVideoFrame, uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromVideoFrame(aVideoFrame, aSurfaceFlags, target);
+ }
+ // This function can be called on any thread.
+ static mozilla::SurfaceFromElementResult SurfaceFromImageBitmap(
+ mozilla::dom::ImageBitmap* aImageBitmap, uint32_t aSurfaceFlags);
+
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::Element* aElement,
+ const mozilla::Maybe<int32_t>& aResizeWidth,
+ const mozilla::Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::Element* aElement, uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromElement(aElement, mozilla::Nothing(), mozilla::Nothing(),
+ aSurfaceFlags, target);
+ }
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::Element* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget) {
+ return SurfaceFromElement(aElement, mozilla::Nothing(), mozilla::Nothing(),
+ aSurfaceFlags, aTarget);
+ }
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::Element* aElement,
+ const mozilla::Maybe<int32_t>& aResizeWidth,
+ const mozilla::Maybe<int32_t>& aResizeHeight,
+ uint32_t aSurfaceFlags = 0) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromElement(aElement, aResizeWidth, aResizeHeight,
+ aSurfaceFlags, target);
+ }
+
+ // There are a bunch of callers of SurfaceFromElement. Just mark it as
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ nsIImageLoadingContent* aElement,
+ const mozilla::Maybe<int32_t>& aResizeWidth,
+ const mozilla::Maybe<int32_t>& aResizeHeight, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ // Need an HTMLImageElement overload, because otherwise the
+ // nsIImageLoadingContent and mozilla::dom::Element overloads are ambiguous
+ // for HTMLImageElement.
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::HTMLImageElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::HTMLCanvasElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::HTMLCanvasElement* aElement, uint32_t aSurfaceFlags) {
+ RefPtr<DrawTarget> target = nullptr;
+ return SurfaceFromElement(aElement, aSurfaceFlags, target);
+ }
+ static mozilla::SurfaceFromElementResult SurfaceFromElement(
+ mozilla::dom::HTMLVideoElement* aElement, uint32_t aSurfaceFlags,
+ RefPtr<DrawTarget>& aTarget);
+
+ /**
+ * When the document is editable by contenteditable attribute of its root
+ * content or body content.
+ *
+ * Be aware, this returns nullptr if it's in designMode.
+ *
+ * For example:
+ *
+ * <html contenteditable="true"><body></body></html>
+ * returns the <html>.
+ *
+ * <html><body contenteditable="true"></body></html>
+ * <body contenteditable="true"></body>
+ * With these cases, this returns the <body>.
+ * NOTE: The latter case isn't created normally, however, it can be
+ * created by script with XHTML.
+ *
+ * <body><p contenteditable="true"></p></body>
+ * returns nullptr because <body> isn't editable.
+ */
+ static mozilla::dom::Element* GetEditableRootContentByContentEditable(
+ mozilla::dom::Document* aDocument);
+
+ static void AddExtraBackgroundItems(nsDisplayListBuilder* aBuilder,
+ nsDisplayList* aList, nsIFrame* aFrame,
+ const nsRect& aCanvasArea,
+ const nsRegion& aVisibleRegion,
+ nscolor aBackstop);
+
+ /**
+ * Returns true if the passed in prescontext needs the dark grey background
+ * that goes behind the page of a print preview presentation.
+ */
+ static bool NeedsPrintPreviewBackground(nsPresContext* aPresContext);
+
+ /**
+ * Types used by the helpers for InspectorUtils.getUsedFontFaces.
+ * The API returns an array (UsedFontFaceList) that owns the
+ * InspectorFontFace instances, but during range traversal we also
+ * want to maintain a mapping from gfxFontEntry to InspectorFontFace
+ * records, so use a temporary hashtable for that.
+ */
+ typedef nsTArray<mozilla::UniquePtr<mozilla::dom::InspectorFontFace>>
+ UsedFontFaceList;
+ typedef nsTHashMap<nsPtrHashKey<gfxFontEntry>,
+ mozilla::dom::InspectorFontFace*>
+ UsedFontFaceTable;
+
+ /**
+ * Adds all font faces used in the frame tree starting from aFrame
+ * to the list aFontFaceList.
+ * aMaxRanges: maximum number of text ranges to record for each face.
+ */
+ static nsresult GetFontFacesForFrames(nsIFrame* aFrame,
+ UsedFontFaceList& aResult,
+ UsedFontFaceTable& aFontFaces,
+ uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace);
+
+ /**
+ * Adds all font faces used within the specified range of text in aFrame,
+ * and optionally its continuations, to the list in aFontFaceList.
+ * Pass 0 and INT32_MAX for aStartOffset and aEndOffset to specify the
+ * entire text is to be considered.
+ * aMaxRanges: maximum number of text ranges to record for each face.
+ */
+ static void GetFontFacesForText(nsIFrame* aFrame, int32_t aStartOffset,
+ int32_t aEndOffset, bool aFollowContinuations,
+ UsedFontFaceList& aResult,
+ UsedFontFaceTable& aFontFaces,
+ uint32_t aMaxRanges,
+ bool aSkipCollapsedWhitespace);
+
+ /**
+ * Walks the frame tree starting at aFrame looking for textRuns.
+ * If |clear| is true, just clears the TEXT_RUN_MEMORY_ACCOUNTED flag
+ * on each textRun found (and |aMallocSizeOf| is not used).
+ * If |clear| is false, adds the storage used for each textRun to the
+ * total, and sets the TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double-
+ * accounting. (Runs with this flag already set will be skipped.)
+ * Expected usage pattern is therefore to call twice:
+ * (void)SizeOfTextRunsForFrames(rootFrame, nullptr, true);
+ * total = SizeOfTextRunsForFrames(rootFrame, mallocSizeOf, false);
+ */
+ static size_t SizeOfTextRunsForFrames(nsIFrame* aFrame,
+ mozilla::MallocSizeOf aMallocSizeOf,
+ bool clear);
+
+ /**
+ * Returns true if |aFrame| has an animation of a property in |aPropertySet|
+ * regardless of whether any property in the set is overridden by an
+ * !important rule.
+ */
+ static bool HasAnimationOfPropertySet(const nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet);
+
+ /**
+ * A variant of the above HasAnimationOfPropertySet that takes an optional
+ * EffectSet parameter as an optimization to save redundant lookups of the
+ * EffectSet.
+ */
+ static bool HasAnimationOfPropertySet(const nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet,
+ mozilla::EffectSet* aEffectSet);
+
+ /**
+ * A variant of the above HasAnimationOfPropertySet. This is especially for
+ * tranform-like properties with motion-path.
+ * For transform-like properties with motion-path, we need to check if
+ * offset-path has effect. If we don't have any animation on offset-path and
+ * offset-path is none, there is no effective motion-path, and so we don't
+ * care other offset-* properties. In this case, this function only checks the
+ * rest of transform-like properties (i.e. transform/translate/rotate/scale).
+ */
+ static bool HasAnimationOfTransformAndMotionPath(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if |aFrame| has an animation of |aProperty| which is
+ * not overridden by !important rules.
+ */
+ static bool HasEffectiveAnimation(const nsIFrame* aFrame,
+ nsCSSPropertyID aProperty);
+
+ /**
+ * Returns true if |aFrame| has an animation where at least one of the
+ * properties in |aPropertySet| is not overridden by !important rules.
+ *
+ * If |aPropertySet| includes transform-like properties (transform, rotate,
+ * etc.) however, this will return false if any of the transform-like
+ * properties is overriden by an !important rule since these properties should
+ * be combined on the compositor.
+ */
+ static bool HasEffectiveAnimation(const nsIFrame* aFrame,
+ const nsCSSPropertyIDSet& aPropertySet);
+
+ /**
+ * Returns all effective animated CSS properties on |aStyleFrame| and its
+ * corresponding primary frame (for content that makes this distinction,
+ * notable display:table content) that can be animated on the compositor.
+ *
+ * Properties that can be animated on the compositor but which are overridden
+ * by !important rules are not returned.
+ *
+ * Unlike HasEffectiveAnimation, however, this does not check the set of
+ * transform-like properties to ensure that if any such properties are
+ * overridden by !important rules, the other transform-like properties are
+ * not run on the compositor (see bug 1534884).
+ */
+ static nsCSSPropertyIDSet GetAnimationPropertiesForCompositor(
+ const nsIFrame* aStyleFrame);
+
+ /**
+ * Checks if off-main-thread animations are enabled.
+ */
+ static bool AreAsyncAnimationsEnabled();
+
+ /**
+ * Checks if retained display lists are enabled.
+ */
+ static bool AreRetainedDisplayListsEnabled();
+
+ static bool DisplayRootHasRetainedDisplayListBuilder(nsIFrame* aFrame);
+
+ static RetainedDisplayListBuilder* GetRetainedDisplayListBuilder(
+ nsIFrame* aFrame);
+
+ /**
+ * Find a suitable scale for a element (aFrame's content) over the course of
+ * any animations and transitions of the CSS transform property on the element
+ * that run on the compositor thread. It will check the maximum and minimum
+ * scale during the animations and transitions and return a suitable value for
+ * performance and quality. Will return scale(1,1) if there are no such
+ * animations. Always returns a positive value.
+ * @param aVisibleSize is the size of the area we want to paint
+ * @param aDisplaySize is the size of the display area of the pres context
+ */
+ static MatrixScales ComputeSuitableScaleForAnimation(
+ const nsIFrame* aFrame, const nsSize& aVisibleSize,
+ const nsSize& aDisplaySize);
+
+ /**
+ * Checks whether we want to use the GPU to scale images when
+ * possible.
+ */
+ static bool GPUImageScalingEnabled();
+
+ /**
+ * Unions the overflow areas of the children of aFrame with aOverflowAreas.
+ * aSkipChildLists specifies any child lists that should be skipped.
+ * FrameChildListID::Popup is always skipped.
+ */
+ static void UnionChildOverflow(
+ nsIFrame* aFrame, mozilla::OverflowAreas& aOverflowAreas,
+ mozilla::FrameChildListIDs aSkipChildLists = {});
+
+ /**
+ * Return the font size inflation *ratio* for a given frame. This is
+ * the factor by which font sizes should be inflated; it is never
+ * smaller than 1.
+ */
+ static float FontSizeInflationFor(const nsIFrame* aFrame);
+
+ /**
+ * Perform the first half of the computation of FontSizeInflationFor
+ * (see above).
+ * This includes determining whether inflation should be performed
+ * within this container and returning 0 if it should not be.
+ *
+ * The result is guaranteed not to vary between line participants
+ * (inlines, text frames) within a line.
+ *
+ * The result should not be used directly since font sizes slightly
+ * above the minimum should always be adjusted as done by
+ * FontSizeInflationInner.
+ */
+ static nscoord InflationMinFontSizeFor(const nsIFrame* aFrame);
+
+ /**
+ * Perform the second half of the computation done by
+ * FontSizeInflationFor (see above).
+ *
+ * aMinFontSize must be the result of one of the
+ * InflationMinFontSizeFor methods above.
+ */
+ static float FontSizeInflationInner(const nsIFrame* aFrame,
+ nscoord aMinFontSize);
+
+ static bool FontSizeInflationEnabled(nsPresContext* aPresContext);
+
+ /**
+ * Returns true if the nglayout.debug.invalidation pref is set to true.
+ */
+ static bool InvalidationDebuggingIsEnabled() {
+ return mozilla::StaticPrefs::nglayout_debug_invalidation() ||
+ getenv("MOZ_DUMP_INVALIDATION") != 0;
+ }
+
+ static void Initialize();
+ static void Shutdown();
+
+ /**
+ * Register an imgIRequest object with a refresh driver.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * register with.
+ * @param aRequest A pointer to the imgIRequest object which the client wants
+ * to register with the refresh driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is true, then this request will not be
+ * registered again. If the request is registered by this function,
+ * then *aRequestRegistered will be set to true upon the completion of
+ * this function.
+ *
+ */
+ static void RegisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Register an imgIRequest object with a refresh driver, but only if the
+ * request is for an image that is animated.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * register with.
+ * @param aRequest A pointer to the imgIRequest object which the client wants
+ * to register with the refresh driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is true, then this request will not be
+ * registered again. If the request is registered by this function,
+ * then *aRequestRegistered will be set to true upon the completion of
+ * this function.
+ *
+ */
+ static void RegisterImageRequestIfAnimated(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Deregister an imgIRequest object from a refresh driver.
+ *
+ * @param aPresContext The nsPresContext whose refresh driver we want to
+ * deregister from.
+ * @param aRequest A pointer to the imgIRequest object with which the client
+ * previously registered and now wants to deregister from the refresh
+ * driver.
+ * @param aRequestRegistered A pointer to a boolean value which indicates
+ * whether the given image request is registered. If
+ * *aRequestRegistered is false, then this request will not be
+ * deregistered. If the request is deregistered by this function,
+ * then *aRequestRegistered will be set to false upon the completion of
+ * this function.
+ */
+ static void DeregisterImageRequest(nsPresContext* aPresContext,
+ imgIRequest* aRequest,
+ bool* aRequestRegistered);
+
+ /**
+ * Shim to nsCSSFrameConstructor::PostRestyleEvent. Exists so that we
+ * can avoid including nsCSSFrameConstructor.h and all its dependencies
+ * in content files.
+ */
+ static void PostRestyleEvent(mozilla::dom::Element*, mozilla::RestyleHint,
+ nsChangeHint aMinChangeHint);
+
+ /**
+ * Updates a pair of x and y distances if a given point is closer to a given
+ * rectangle than the original distance values. If aPoint is closer to
+ * aRect than aClosestXDistance and aClosestYDistance indicate, then those
+ * two variables are updated with the distance between aPoint and aRect,
+ * and true is returned. If aPoint is not closer, then aClosestXDistance
+ * and aClosestYDistance are left unchanged, and false is returned.
+ *
+ * Distances are measured in the two dimensions separately; a closer x
+ * distance beats a closer y distance.
+ */
+ template <typename PointType, typename RectType, typename CoordType>
+ static bool PointIsCloserToRect(PointType aPoint, const RectType& aRect,
+ CoordType& aClosestXDistance,
+ CoordType& aClosestYDistance);
+ /**
+ * Computes the box shadow rect for the frame, or returns an empty rect if
+ * there are no shadows.
+ *
+ * @param aFrame Frame to compute shadows for.
+ * @param aFrameSize Size of aFrame (in case it hasn't been set yet).
+ */
+ static nsRect GetBoxShadowRectForFrame(nsIFrame* aFrame,
+ const nsSize& aFrameSize);
+
+#ifdef DEBUG
+ /**
+ * Assert that there are no duplicate continuations of the same frame
+ * within aFrameList. Optimize the tests by assuming that all frames
+ * in aFrameList have parent aContainer.
+ */
+ static void AssertNoDuplicateContinuations(nsIFrame* aContainer,
+ const nsFrameList& aFrameList);
+
+ /**
+ * Assert that the frame tree rooted at |aSubtreeRoot| is empty, i.e.,
+ * that it contains no first-in-flows.
+ */
+ static void AssertTreeOnlyEmptyNextInFlows(nsIFrame* aSubtreeRoot);
+#endif
+
+ /**
+ * Helper method to transform |aBounds| from aFrame to aAncestorFrame,
+ * and combine it with |aPreciseTargetDest| if it is axis-aligned, or
+ * combine it with |aImpreciseTargetDest| if not. The transformed rect is
+ * clipped to |aClip|; if |aClip| has rounded corners, that also causes
+ * the imprecise target to be used.
+ */
+ static void TransformToAncestorAndCombineRegions(
+ const nsRegion& aRegion, nsIFrame* aFrame, const nsIFrame* aAncestorFrame,
+ nsRegion* aPreciseTargetDest, nsRegion* aImpreciseTargetDest,
+ mozilla::Maybe<Matrix4x4Flagged>* aMatrixCache,
+ const mozilla::DisplayItemClip* aClip);
+
+ /**
+ * Populate aOutSize with the size of the content viewer corresponding
+ * to the given prescontext. Return true if the size was set, false
+ * otherwise.
+ */
+ enum class SubtractDynamicToolbar { No, Yes };
+ static bool GetDocumentViewerSize(
+ const nsPresContext* aPresContext, LayoutDeviceIntSize& aOutSize,
+ SubtractDynamicToolbar = SubtractDynamicToolbar::Yes);
+
+ /**
+ * Whether to include the dynamic toolbar area automatically (depending
+ * whether the root container is scrollable or not) or forcibly in below
+ * UpdateCompositionBoundsForRCDRSF and CalculateCompositionSizeForFrame
+ * functions.
+ */
+ enum class IncludeDynamicToolbar { Auto, Force };
+
+ private:
+ static bool UpdateCompositionBoundsForRCDRSF(
+ mozilla::ParentLayerRect& aCompBounds, const nsPresContext* aPresContext,
+ IncludeDynamicToolbar aIncludeDynamicToolbar =
+ IncludeDynamicToolbar::Auto);
+
+ public:
+ /**
+ * Calculate the compostion size for a frame. See FrameMetrics.h for
+ * defintion of composition size (or bounds).
+ * Note that for the root content document's root scroll frame (RCD-RSF),
+ * the returned size does not change as the document's resolution changes,
+ * but for all other frames it does. This means that callers that pass in
+ * a frame that may or may not be the RCD-RSF (which is most callers),
+ * are likely to need special-case handling of the RCD-RSF.
+ */
+ static nsSize CalculateCompositionSizeForFrame(
+ nsIFrame* aFrame, bool aSubtractScrollbars = true,
+ const nsSize* aOverrideScrollPortSize = nullptr,
+ IncludeDynamicToolbar aIncludeDynamicToolbar =
+ IncludeDynamicToolbar::Auto);
+
+ /**
+ * Calculate a size suitable for bounding the size of the composition bounds
+ * of scroll frames in the current process. This should be at most the
+ * composition size of the cross-process RCD-RSF, but it may be a tighter
+ * bounding size.
+ * @param aFrame A frame in the (in-process) root content document (or a
+ * descendant of it).
+ * @param aIsRootContentDocRootScrollFrame Whether aFrame is the root
+ * scroll frame of the *cross-process* root content document.
+ * In this case we just use aFrame's own composition size.
+ * @param aMetrics A partially populated FrameMetrics for aFrame. Must have at
+ * least mCompositionBounds, mCumulativeResolution, and
+ * mDevPixelsPerCSSPixel set.
+ */
+ static CSSSize CalculateBoundingCompositionSize(
+ const nsIFrame* aFrame, bool aIsRootContentDocRootScrollFrame,
+ const FrameMetrics& aMetrics);
+
+ /**
+ * Calculate the scrollable rect for a frame. See FrameMetrics.h for
+ * defintion of scrollable rect. aScrollableFrame is the scroll frame to
+ * calculate the scrollable rect for. If it's null then we calculate the
+ * scrollable rect as the rect of the root frame.
+ */
+ static nsRect CalculateScrollableRectForFrame(
+ const nsIScrollableFrame* aScrollableFrame, const nsIFrame* aRootFrame);
+
+ /**
+ * Calculate the expanded scrollable rect for a frame. See FrameMetrics.h for
+ * defintion of expanded scrollable rect.
+ */
+ static nsRect CalculateExpandedScrollableRect(nsIFrame* aFrame);
+
+ /**
+ * Returns true if the widget owning the given frame uses asynchronous
+ * scrolling.
+ */
+ static bool UsesAsyncScrolling(nsIFrame* aFrame);
+
+ /**
+ * Returns true if the widget owning the given frame has builtin APZ support
+ * enabled.
+ */
+ static bool AsyncPanZoomEnabled(const nsIFrame* aFrame);
+
+ /**
+ * Returns true if aDocument should be allowed to use resolution
+ * zooming.
+ */
+ static bool AllowZoomingForDocument(const mozilla::dom::Document* aDocument);
+
+ /**
+ * Returns true if we need to disable async scrolling for this particular
+ * element. Note that this does a partial disabling - the displayport still
+ * exists but uses a very small margin, and the compositor doesn't apply the
+ * async transform. However, the content may still be layerized.
+ */
+ static bool ShouldDisableApzForElement(nsIContent* aContent);
+
+ /**
+ * Log a key/value pair as "additional data" (not associated with a paint)
+ * for APZ testing.
+ * While the data is not associated with a paint, the APZTestData object
+ * is still owned by {Client,WebRender}LayerManager, so we need to be passed
+ * something from which we can derive the layer manager.
+ * This function takes a display list builder as the object to derive the
+ * layer manager from, to facilitate logging test data during display list
+ * building, but other overloads that take other objects could be added if
+ * desired.
+ */
+ static void LogAdditionalTestData(nsDisplayListBuilder* aBuilder,
+ const std::string& aKey,
+ const std::string& aValue);
+
+ /**
+ * Log a key/value pair for APZ testing during a paint.
+ * @param aManager The data will be written to the APZTestData associated
+ * with this layer manager.
+ * @param aScrollId Identifies the scroll frame to which the data pertains.
+ * @param aKey The key under which to log the data.
+ * @param aValue The value of the data to be logged.
+ */
+ static void LogTestDataForPaint(
+ mozilla::layers::WebRenderLayerManager* aManager, ViewID aScrollId,
+ const std::string& aKey, const std::string& aValue) {
+ DoLogTestDataForPaint(aManager, aScrollId, aKey, aValue);
+ }
+
+ /**
+ * A convenience overload of LogTestDataForPaint() that accepts any type
+ * as the value, and passes it through mozilla::ToString() to obtain a string
+ * value. The type passed must support streaming to an std::ostream.
+ */
+ template <typename Value>
+ static void LogTestDataForPaint(
+ mozilla::layers::WebRenderLayerManager* aManager, ViewID aScrollId,
+ const std::string& aKey, const Value& aValue) {
+ DoLogTestDataForPaint(aManager, aScrollId, aKey, mozilla::ToString(aValue));
+ }
+
+ /**
+ * Calculate a basic FrameMetrics with enough fields set to perform some
+ * layout calculations. The fields set are dev-to-css ratio, pres shell
+ * resolution, cumulative resolution, zoom, composition size, root
+ * composition size, scroll offset and scrollable rect.
+ *
+ * Note that for the RCD-RSF, the scroll offset returned is the layout
+ * viewport offset; if you need the visual viewport offset, that needs to
+ * be queried independently via PresShell::GetVisualViewportOffset().
+ *
+ * By contrast, ComputeFrameMetrics() computes all the fields, but requires
+ * extra inputs and can only be called during frame layer building.
+ */
+ static FrameMetrics CalculateBasicFrameMetrics(
+ nsIScrollableFrame* aScrollFrame);
+
+ static nsIScrollableFrame* GetAsyncScrollableAncestorFrame(nsIFrame* aTarget);
+
+ static void SetBSizeFromFontMetrics(
+ const nsIFrame* aFrame, mozilla::ReflowOutput& aMetrics,
+ const mozilla::LogicalMargin& aFramePadding, mozilla::WritingMode aLineWM,
+ mozilla::WritingMode aFrameWM);
+
+ static bool HasDocumentLevelListenersForApzAwareEvents(PresShell* aPresShell);
+
+ /**
+ * Returns true if the given scroll origin is "higher priority" than APZ.
+ * In general any content programmatic scrolls (e.g. scrollTo calls) are
+ * higher priority, and take precedence over APZ scrolling. This function
+ * returns true for those, and returns false for other origins like APZ
+ * itself, or scroll position updates from the history restore code.
+ */
+ static bool CanScrollOriginClobberApz(ScrollOrigin aScrollOrigin);
+
+ static ScrollMetadata ComputeScrollMetadata(
+ const nsIFrame* aForFrame, const nsIFrame* aScrollFrame,
+ nsIContent* aContent, const nsIFrame* aItemFrame,
+ const nsPoint& aOffsetToReferenceFrame,
+ mozilla::layers::WebRenderLayerManager* aLayerManager,
+ ViewID aScrollParentId, const nsSize& aScrollPortSize, bool aIsRoot);
+
+ /**
+ * Returns the metadata to put onto the root layer of a layer tree, if one is
+ * needed. The last argument is a callback function to check if the caller
+ * already has a metadata for a given scroll id.
+ */
+ static mozilla::Maybe<ScrollMetadata> GetRootMetadata(
+ nsDisplayListBuilder* aBuilder,
+ mozilla::layers::WebRenderLayerManager* aLayerManager,
+ const std::function<bool(ViewID& aScrollId)>& aCallback);
+
+ /**
+ * If the given scroll frame needs an area excluded from its composition
+ * bounds due to scrollbars, return that area, otherwise return an empty
+ * margin.
+ * There is no need to exclude scrollbars in the following cases:
+ * - If the scroll frame is not the RCD-RSF; in that case, the composition
+ * bounds is calculated based on the scroll port which already excludes
+ * the scrollbar area.
+ * - If the scrollbars are overlay, since then they are drawn on top of the
+ * scrollable content.
+ */
+ static nsMargin ScrollbarAreaToExcludeFromCompositionBoundsFor(
+ const nsIFrame* aScrollFrame);
+
+ static bool ShouldUseNoFramesSheet(mozilla::dom::Document*);
+
+ /**
+ * Get the text content inside the frame. This methods traverse the
+ * frame tree and collect the content from text frames. Note that this
+ * method is similiar to nsContentUtils::GetNodeTextContent, but it at
+ * least differs from that method in the following things:
+ * 1. it skips text content inside nodes like style, script, textarea
+ * which don't generate an in-tree text frame for the text;
+ * 2. it skips elements with display property set to none;
+ * 3. it skips out-of-flow elements;
+ * 4. it includes content inside pseudo elements;
+ * 5. it may include part of text content of a node if a text frame
+ * inside is split to different continuations.
+ */
+ static void GetFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
+ /**
+ * Same as GetFrameTextContent but appends the result rather than sets it.
+ */
+ static void AppendFrameTextContent(nsIFrame* aFrame, nsAString& aResult);
+
+ /**
+ * Takes a selection, and returns selection's bounding rect which is relative
+ * to its root frame.
+ *
+ * @param aSel Selection to check
+ */
+ static nsRect GetSelectionBoundingRect(const mozilla::dom::Selection* aSel);
+
+ /**
+ * Calculate the bounding rect of |aContent|, relative to the origin
+ * of the scrolled content of |aRootScrollFrame|.
+ * Where the element is contained inside a scrollable subframe, the
+ * bounding rect is clipped to the bounds of the subframe.
+ * If non-null aOutNearestScrollClip will be filled in with the rect of the
+ * nearest scroll frame (excluding aRootScrollFrame) that is an ancestor of
+ * the frame of aContent, if such exists, in the same coords are the returned
+ * rect. This rect is used to clip the result.
+ */
+ static CSSRect GetBoundingContentRect(
+ const nsIContent* aContent, const nsIScrollableFrame* aRootScrollFrame,
+ mozilla::Maybe<CSSRect>* aOutNearestScrollClip = nullptr);
+
+ /**
+ * Similar to GetBoundingContentRect for nsIFrame.
+ */
+ static CSSRect GetBoundingFrameRect(
+ nsIFrame* aFrame, const nsIScrollableFrame* aRootScrollFrame,
+ mozilla::Maybe<CSSRect>* aOutNearestScrollClip = nullptr);
+
+ /**
+ * Returns the first ancestor who is a float containing block.
+ */
+ static nsBlockFrame* GetFloatContainingBlock(nsIFrame* aFrame);
+
+ /**
+ * Walks up the frame tree from |aForFrame| up to |aTopFrame|, or to the
+ * root of the frame tree if |aTopFrame| is nullptr, and returns true if
+ * a transformed frame is encountered.
+ */
+ static bool IsTransformed(nsIFrame* aForFrame, nsIFrame* aTopFrame = nullptr);
+
+ /**
+ * Walk up from aFrame to the cross-doc root, accumulating all the APZ
+ * callback transforms on the content elements encountered along the way.
+ * Return the accumulated value.
+ * XXX: Note that this does not take into account CSS transforms, nor
+ * differences in structure between the frame tree and the layer tree (which
+ * is probably what we *want* to be computing).
+ */
+ static CSSPoint GetCumulativeApzCallbackTransform(nsIFrame* aFrame);
+
+ /**
+ * Compute a rect to pre-render in cases where we want to render more of
+ * something than what is visible (usually to support async transformation).
+ * @param aFrame the target frame to be pre-rendered
+ * @param aDirtyRect the area that's visible in the coordinate system of
+ * |aFrame|.
+ * @param aOverflow the total size of the thing we're rendering in the
+ * coordinate system of |aFrame|.
+ * @param aPrerenderSize how large of an area we're willing to render in the
+ * coordinate system of the root frame.
+ * @return A rectangle that includes |aDirtyRect|, is clamped to |aOverflow|,
+ * and is no larger than |aPrerenderSize| (unless |aPrerenderSize| is
+ * smaller than |aDirtyRect|, in which case the returned rect will
+ * still include |aDirtyRect| and thus be larger than
+ * |aPrerenderSize|).
+ */
+ static nsRect ComputePartialPrerenderArea(nsIFrame* aFrame,
+ const nsRect& aDirtyRect,
+ const nsRect& aOverflow,
+ const nsSize& aPrerenderSize);
+
+ /*
+ * Checks whether a node is an invisible break.
+ * If not, returns the first frame on the next line if such a next line
+ * exists.
+ *
+ * @return
+ * true if the node is an invisible break. aNextLineFrame is returned null
+ * in this case.
+ *
+ * false if the node causes a visible break or if the node is no break.
+ *
+ * @param aNextLineFrame
+ * assigned to first frame on the next line if such a next line exists, null
+ * otherwise.
+ */
+ static bool IsInvisibleBreak(nsINode* aNode,
+ nsIFrame** aNextLineFrame = nullptr);
+
+ static nsRect ComputeSVGOriginBox(mozilla::dom::SVGViewportElement*);
+
+ // Compute the geometry box for SVG layout. The caller should map the CSS box
+ // into the proper SVG box.
+ static nsRect ComputeSVGReferenceRect(nsIFrame*, StyleGeometryBox);
+
+ // Compute the geometry box for CSS layout. The caller should map the SVG box
+ // into the proper CSS box.
+ static nsRect ComputeHTMLReferenceRect(const nsIFrame*, StyleGeometryBox);
+
+ static nsRect ComputeClipPathGeometryBox(
+ nsIFrame*, const mozilla::StyleShapeGeometryBox&);
+
+ static nsPoint ComputeOffsetToUserSpace(nsDisplayListBuilder* aBuilder,
+ nsIFrame* aFrame);
+
+ // Callers are responsible to ensure the user-font-set is up-to-date if
+ // aUseUserFontSet is true.
+ static already_AddRefed<nsFontMetrics> GetMetricsFor(
+ nsPresContext* aPresContext, bool aIsVertical,
+ const nsStyleFont* aStyleFont, mozilla::Length aFontSize,
+ bool aUseUserFontSet);
+
+ static void ComputeSystemFont(nsFont* aSystemFont,
+ mozilla::StyleSystemFont aFontID,
+ const nsFont& aDefaultVariableFont,
+ const mozilla::dom::Document* aDocument);
+
+ static uint32_t ParseFontLanguageOverride(const nsAString& aLangTag);
+
+ /**
+ * Returns true if there are any preferences or overrides that indicate a
+ * need to handle <meta name="viewport"> tags.
+ */
+ static bool ShouldHandleMetaViewport(const mozilla::dom::Document*);
+
+ /**
+ * Resolve a CSS <length-percentage> value to a definite size.
+ */
+ template <bool clampNegativeResultToZero>
+ static nscoord ResolveToLength(const LengthPercentage& aLengthPercentage,
+ nscoord aPercentageBasis) {
+ nscoord value = (aPercentageBasis == NS_UNCONSTRAINEDSIZE)
+ ? aLengthPercentage.Resolve(0)
+ : aLengthPercentage.Resolve(aPercentageBasis);
+ return clampNegativeResultToZero ? std::max(0, value) : value;
+ }
+
+ /**
+ * Resolve a column-gap/row-gap to a definite size.
+ * @note This method resolves 'normal' to zero.
+ * Callers who want different behavior should handle 'normal' on their own.
+ */
+ static nscoord ResolveGapToLength(
+ const mozilla::NonNegativeLengthPercentageOrNormal& aGap,
+ nscoord aPercentageBasis) {
+ if (aGap.IsNormal()) {
+ return nscoord(0);
+ }
+ return ResolveToLength<true>(aGap.AsLengthPercentage(), aPercentageBasis);
+ }
+
+ /**
+ * Get the computed style from which the scrollbar style should be
+ * used for the given scrollbar part frame.
+ */
+ static ComputedStyle* StyleForScrollbar(const nsIFrame* aScrollbarPart);
+
+ /**
+ * Returns true if |aFrame| is scrolled out of view by a scrollable element in
+ * a cross-process ancestor document.
+ * Note this function only works for frames in out-of-process iframes.
+ **/
+ static bool FrameIsScrolledOutOfViewInCrossProcess(const nsIFrame* aFrame);
+
+ /**
+ * Similar to above FrameIsScrolledOutViewInCrossProcess but returns true even
+ * if |aFrame| is not fully scrolled out of view and its visible area width or
+ * height is smaller than |aMargin|.
+ **/
+ static bool FrameIsMostlyScrolledOutOfViewInCrossProcess(
+ const nsIFrame* aFrame, nscoord aMargin);
+
+ /**
+ * Expand the height of |aSize| to the size of `vh` units.
+ *
+ * With dynamic toolbar(s) the height for `vh` units is greater than the
+ * ICB height, we need to expand it in some places.
+ **/
+ static nsSize ExpandHeightForViewportUnits(nsPresContext* aPresContext,
+ const nsSize& aSize);
+
+ static CSSSize ExpandHeightForDynamicToolbar(
+ const nsPresContext* aPresContext, const CSSSize& aSize);
+ static nsSize ExpandHeightForDynamicToolbar(const nsPresContext* aPresContext,
+ const nsSize& aSize);
+
+ /**
+ * Returns the nsIFrame which clips overflow regions of the given |aFrame|.
+ * Note CSS clip or clip-path isn't accounted for.
+ **/
+ static nsIFrame* GetNearestOverflowClipFrame(nsIFrame* aFrame);
+
+ /*
+ * Returns true if the user's preferences allow for smooth scrolling.
+ */
+ static bool IsSmoothScrollingEnabled();
+
+ /*
+ * Recompute the default value of general.smoothScroll based on
+ * the system settings for prefers-reduced-motion.
+ *
+ * Note: Must only be called from the main thread.
+ */
+ static void RecomputeSmoothScrollDefault();
+
+ private:
+ /**
+ * Helper function for LogTestDataForPaint().
+ */
+ static void DoLogTestDataForPaint(
+ mozilla::layers::WebRenderLayerManager* aManager, ViewID aScrollId,
+ const std::string& aKey, const std::string& aValue);
+
+ static bool IsAPZTestLoggingEnabled();
+
+ static void ConstrainToCoordValues(gfxFloat& aStart, gfxFloat& aSize);
+ static void ConstrainToCoordValues(float& aStart, float& aSize);
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsLayoutUtils::PaintFrameFlags)
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsLayoutUtils::GetPopupFrameForPointFlags)
+
+template <typename PointType, typename RectType, typename CoordType>
+/* static */ bool nsLayoutUtils::PointIsCloserToRect(
+ PointType aPoint, const RectType& aRect, CoordType& aClosestXDistance,
+ CoordType& aClosestYDistance) {
+ CoordType fromLeft = aPoint.x - aRect.x;
+ CoordType fromRight = aPoint.x - aRect.XMost();
+
+ CoordType xDistance;
+ if (fromLeft >= 0 && fromRight <= 0) {
+ xDistance = 0;
+ } else {
+ xDistance = std::min(abs(fromLeft), abs(fromRight));
+ }
+
+ if (xDistance <= aClosestXDistance) {
+ if (xDistance < aClosestXDistance) {
+ aClosestYDistance = std::numeric_limits<CoordType>::max();
+ }
+
+ CoordType fromTop = aPoint.y - aRect.y;
+ CoordType fromBottom = aPoint.y - aRect.YMost();
+
+ CoordType yDistance;
+ if (fromTop >= 0 && fromBottom <= 0) {
+ yDistance = 0;
+ } else {
+ yDistance = std::min(abs(fromTop), abs(fromBottom));
+ }
+
+ if (yDistance < aClosestYDistance) {
+ aClosestXDistance = xDistance;
+ aClosestYDistance = yDistance;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+template <typename T>
+nsRect nsLayoutUtils::RoundGfxRectToAppRect(const T& aRect,
+ const float aFactor) {
+ // Get a new Rect whose units are app units by scaling by the specified
+ // factor.
+ T scaledRect = aRect;
+ scaledRect.ScaleRoundOut(aFactor);
+
+ // We now need to constrain our results to the max and min values for coords.
+ ConstrainToCoordValues(scaledRect.x, scaledRect.width);
+ ConstrainToCoordValues(scaledRect.y, scaledRect.height);
+
+ if (!aRect.Width()) {
+ scaledRect.SetWidth(0);
+ }
+
+ if (!aRect.Height()) {
+ scaledRect.SetHeight(0);
+ }
+
+ // Now typecast everything back. This is guaranteed to be safe.
+ return nsRect(nscoord(scaledRect.X()), nscoord(scaledRect.Y()),
+ nscoord(scaledRect.Width()), nscoord(scaledRect.Height()));
+}
+
+namespace mozilla {
+
+/**
+ * Converts an nsPoint in app units to a Moz2D Point in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ */
+inline gfx::Point NSPointToPoint(const nsPoint& aPoint,
+ int32_t aAppUnitsPerPixel) {
+ return gfx::Point(gfx::Float(aPoint.x) / aAppUnitsPerPixel,
+ gfx::Float(aPoint.y) / aAppUnitsPerPixel);
+}
+
+/**
+ * Converts an nsRect in app units to a Moz2D Rect in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ */
+gfx::Rect NSRectToRect(const nsRect& aRect, double aAppUnitsPerPixel);
+
+/**
+ * Converts an nsRect in app units to a Moz2D Rect in pixels (whether those
+ * are device pixels or CSS px depends on what the caller chooses to pass as
+ * aAppUnitsPerPixel).
+ *
+ * The passed DrawTarget is used to additionally snap the returned Rect to
+ * device pixels, if appropriate (as decided and carried out by Moz2D's
+ * MaybeSnapToDevicePixels helper, which this function calls to do any
+ * snapping).
+ */
+gfx::Rect NSRectToSnappedRect(const nsRect& aRect, double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT);
+
+/**
+ * Converts, where possible, an nsRect in app units to a Moz2D Rect in pixels
+ * (whether those are device pixels or CSS px depends on what the caller
+ * chooses to pass as aAppUnitsPerPixel).
+ *
+ * If snapping results in a rectangle with zero width or height, the affected
+ * coordinates are left unsnapped
+ */
+gfx::Rect NSRectToNonEmptySnappedRect(const nsRect& aRect,
+ double aAppUnitsPerPixel,
+ const gfx::DrawTarget& aSnapDT);
+
+void StrokeLineWithSnapping(
+ const nsPoint& aP1, const nsPoint& aP2, int32_t aAppUnitsPerDevPixel,
+ gfx::DrawTarget& aDrawTarget, const gfx::Pattern& aPattern,
+ const gfx::StrokeOptions& aStrokeOptions = gfx::StrokeOptions(),
+ const gfx::DrawOptions& aDrawOptions = gfx::DrawOptions());
+
+namespace layout {
+
+/**
+ * An RAII class which will, for the duration of its lifetime,
+ * **if** the frame given is a container for font size inflation,
+ * set the current inflation container on the pres context to null
+ * (and then, in its destructor, restore the old value).
+ */
+class AutoMaybeDisableFontInflation {
+ public:
+ explicit AutoMaybeDisableFontInflation(nsIFrame* aFrame);
+
+ ~AutoMaybeDisableFontInflation();
+
+ private:
+ nsPresContext* mPresContext;
+ bool mOldValue;
+};
+
+} // namespace layout
+} // namespace mozilla
+
+class nsSetAttrRunnable : public mozilla::Runnable {
+ public:
+ nsSetAttrRunnable(mozilla::dom::Element* aElement, nsAtom* aAttrName,
+ const nsAString& aValue);
+ nsSetAttrRunnable(mozilla::dom::Element* aElement, nsAtom* aAttrName,
+ int32_t aValue);
+
+ NS_DECL_NSIRUNNABLE
+
+ RefPtr<mozilla::dom::Element> mElement;
+ RefPtr<nsAtom> mAttrName;
+ nsAutoString mValue;
+};
+
+class nsUnsetAttrRunnable : public mozilla::Runnable {
+ public:
+ nsUnsetAttrRunnable(mozilla::dom::Element* aElement, nsAtom* aAttrName);
+
+ NS_DECL_NSIRUNNABLE
+
+ RefPtr<mozilla::dom::Element> mElement;
+ RefPtr<nsAtom> mAttrName;
+};
+
+// This class allows you to easily set any pointer variable and ensure it's
+// set to nullptr when leaving its scope.
+template <typename T>
+class MOZ_RAII SetAndNullOnExit {
+ public:
+ SetAndNullOnExit(T*& aVariable, T* aValue) {
+ aVariable = aValue;
+ mVariable = &aVariable;
+ }
+ ~SetAndNullOnExit() { *mVariable = nullptr; }
+
+ private:
+ T** mVariable;
+};
+
+#endif // nsLayoutUtils_h__
diff --git a/layout/base/nsPresArena.cpp b/layout/base/nsPresArena.cpp
new file mode 100644
index 0000000000..c93da58179
--- /dev/null
+++ b/layout/base/nsPresArena.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/.
+ */
+
+/* arena allocation for the frame tree and closely-related objects */
+
+#include "nsPresArena.h"
+
+#include "mozilla/Poison.h"
+#include "nsDebug.h"
+#include "nsDisplayList.h"
+#include "nsPrintfCString.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/ComputedStyle.h"
+#include "mozilla/ComputedStyleInlines.h"
+#include "nsWindowSizes.h"
+
+#include <inttypes.h>
+
+using namespace mozilla;
+
+template <size_t ArenaSize, typename ObjectId, size_t ObjectIdCount>
+nsPresArena<ArenaSize, ObjectId, ObjectIdCount>::~nsPresArena() {
+#if defined(MOZ_HAVE_MEM_CHECKS)
+ for (FreeList* entry = mFreeLists; entry != ArrayEnd(mFreeLists); ++entry) {
+ for (void* result : entry->mEntries) {
+ MOZ_MAKE_MEM_UNDEFINED(result, entry->mEntrySize);
+ }
+ entry->mEntries.Clear();
+ }
+#endif
+}
+
+template <size_t ArenaSize, typename ObjectId, size_t ObjectIdCount>
+void* nsPresArena<ArenaSize, ObjectId, ObjectIdCount>::Allocate(ObjectId aCode,
+ size_t aSize) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(aSize > 0, "PresArena cannot allocate zero bytes");
+ MOZ_ASSERT(size_t(aCode) < ArrayLength(mFreeLists));
+
+ // We only hand out aligned sizes
+ aSize = mPool.AlignedSize(aSize);
+
+ FreeList* list = &mFreeLists[size_t(aCode)];
+
+ nsTArray<void*>::index_type len = list->mEntries.Length();
+ if (list->mEntrySize == 0) {
+ MOZ_ASSERT(len == 0, "list with entries but no recorded size");
+ list->mEntrySize = aSize;
+ } else {
+ MOZ_ASSERT(list->mEntrySize == aSize,
+ "different sizes for same object type code");
+ }
+
+ void* result;
+ if (len > 0) {
+ // Remove from the end of the mEntries array to avoid memmoving entries,
+ // and use SetLengthAndRetainStorage to avoid a lot of malloc/free
+ // from ShrinkCapacity on smaller sizes. 500 pointers means the malloc size
+ // for the array is 4096 bytes or more on a 64-bit system. The next smaller
+ // size is 2048 (with jemalloc), which we consider not worth compacting.
+ result = list->mEntries.Elements()[len - 1];
+ if (list->mEntries.Capacity() > 500) {
+ list->mEntries.RemoveElementAtUnsafe(len - 1);
+ } else {
+ list->mEntries.SetLengthAndRetainStorage(len - 1);
+ }
+#if defined(DEBUG)
+ {
+ MOZ_MAKE_MEM_DEFINED(result, list->mEntrySize);
+ char* p = reinterpret_cast<char*>(result);
+ char* limit = p + list->mEntrySize;
+ for (; p < limit; p += sizeof(uintptr_t)) {
+ uintptr_t val = *reinterpret_cast<uintptr_t*>(p);
+ if (val != mozPoisonValue()) {
+ MOZ_ReportAssertionFailure(
+ nsPrintfCString("PresArena: poison overwritten; "
+ "wanted %.16" PRIx64 " "
+ "found %.16" PRIx64 " "
+ "errors in bits %.16" PRIx64 " ",
+ uint64_t(mozPoisonValue()), uint64_t(val),
+ uint64_t(mozPoisonValue() ^ val))
+ .get(),
+ __FILE__, __LINE__);
+ MOZ_CRASH();
+ }
+ }
+ }
+#endif
+ MOZ_MAKE_MEM_UNDEFINED(result, list->mEntrySize);
+ return result;
+ }
+
+ // Allocate a new chunk from the arena
+ list->mEntriesEverAllocated++;
+ return mPool.Allocate(aSize);
+}
+
+template <size_t ArenaSize, typename ObjectId, size_t ObjectIdCount>
+void nsPresArena<ArenaSize, ObjectId, ObjectIdCount>::Free(ObjectId aCode,
+ void* aPtr) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(size_t(aCode) < ArrayLength(mFreeLists));
+
+ // Try to recycle this entry.
+ FreeList* list = &mFreeLists[size_t(aCode)];
+ MOZ_ASSERT(list->mEntrySize > 0, "object of this type was never allocated");
+
+ mozWritePoison(aPtr, list->mEntrySize);
+
+ MOZ_MAKE_MEM_NOACCESS(aPtr, list->mEntrySize);
+ list->mEntries.AppendElement(aPtr);
+}
+
+template <size_t ArenaSize, typename ObjectId, size_t ObjectIdCount>
+void nsPresArena<ArenaSize, ObjectId, ObjectIdCount>::AddSizeOfExcludingThis(
+ nsWindowSizes& aSizes, ArenaKind aKind) const {
+ // We do a complicated dance here because we want to measure the
+ // space taken up by the different kinds of objects in the arena,
+ // but we don't have pointers to those objects. And even if we did,
+ // we wouldn't be able to use mMallocSizeOf on them, since they were
+ // allocated out of malloc'd chunks of memory. So we compute the
+ // size of the arena as known by malloc and we add up the sizes of
+ // all the objects that we care about. Subtracting these two
+ // quantities gives us a catch-all "other" number, which includes
+ // slop in the arena itself as well as the size of objects that
+ // we've not measured explicitly.
+
+ size_t mallocSize = mPool.SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ size_t totalSizeInFreeLists = 0;
+ for (const FreeList* entry = mFreeLists; entry != ArrayEnd(mFreeLists);
+ ++entry) {
+ mallocSize += entry->SizeOfExcludingThis(aSizes.mState.mMallocSizeOf);
+
+ // Note that we're not measuring the size of the entries on the free
+ // list here. The free list knows how many objects we've allocated
+ // ever (which includes any objects that may be on the FreeList's
+ // |mEntries| at this point) and we're using that to determine the
+ // total size of objects allocated with a given ID.
+ size_t totalSize = entry->mEntrySize * entry->mEntriesEverAllocated;
+
+ if (aKind == ArenaKind::PresShell) {
+ switch (entry - mFreeLists) {
+#define PRES_ARENA_OBJECT(name_) \
+ case eArenaObjectID_##name_: \
+ aSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += totalSize; \
+ break;
+#include "nsPresArenaObjectList.h"
+#undef PRES_ARENA_OBJECT
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown arena object type");
+ }
+ } else {
+ MOZ_ASSERT(aKind == ArenaKind::DisplayList);
+ switch (DisplayListArenaObjectId(entry - mFreeLists)) {
+#define DISPLAY_LIST_ARENA_OBJECT(name_) \
+ case DisplayListArenaObjectId::name_: \
+ aSizes.mArenaSizes.NS_ARENA_SIZES_FIELD(name_) += totalSize; \
+ break;
+#include "nsDisplayListArenaTypes.h"
+#undef DISPLAY_LIST_ARENA_OBJECT
+ default:
+ MOZ_ASSERT_UNREACHABLE("Unknown display item arena type");
+ }
+ }
+
+ totalSizeInFreeLists += totalSize;
+ }
+
+ auto& field = aKind == ArenaKind::PresShell
+ ? aSizes.mLayoutPresShellSize
+ : aSizes.mLayoutRetainedDisplayListSize;
+
+ field += mallocSize - totalSizeInFreeLists;
+}
+
+// Explicitly instantiate templates for the used nsPresArena allocator sizes.
+// This is needed because nsPresArena definition is split across multiple files.
+template class nsPresArena<8192, ArenaObjectID, eArenaObjectID_COUNT>;
+template class nsPresArena<32768, DisplayListArenaObjectId,
+ size_t(DisplayListArenaObjectId::COUNT)>;
diff --git a/layout/base/nsPresArena.h b/layout/base/nsPresArena.h
new file mode 100644
index 0000000000..884f2b44c5
--- /dev/null
+++ b/layout/base/nsPresArena.h
@@ -0,0 +1,67 @@
+/* -*- 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/.
+ */
+
+/* arena allocation for the frame tree and closely-related objects */
+
+#ifndef nsPresArena_h___
+#define nsPresArena_h___
+
+#include "mozilla/ArenaAllocator.h"
+#include "mozilla/ArenaObjectID.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/MemoryChecking.h" // Note: Do not remove this, needed for MOZ_HAVE_MEM_CHECKS below
+#include "mozilla/MemoryReporting.h"
+#include <stdint.h>
+#include "nscore.h"
+#include "nsHashKeys.h"
+#include "nsTArray.h"
+#include "nsTHashtable.h"
+
+class nsWindowSizes;
+
+template <size_t ArenaSize, typename ObjectId, size_t ObjectIdCount>
+class nsPresArena {
+ public:
+ nsPresArena() = default;
+ ~nsPresArena();
+
+ /**
+ * Pool allocation with recycler lists indexed by object-type ID (see above).
+ * Every aID must always be used with the same object size, aSize.
+ */
+ void* Allocate(ObjectId aCode, size_t aSize);
+ void Free(ObjectId aCode, void* aPtr);
+
+ enum class ArenaKind { PresShell, DisplayList };
+ /**
+ * Increment nsWindowSizes with sizes of interesting objects allocated in this
+ * arena, and put the general unclassified size in the relevant field
+ * depending on the arena size.
+ */
+ void AddSizeOfExcludingThis(nsWindowSizes&, ArenaKind) const;
+
+ void Check() { mPool.Check(); }
+
+ private:
+ class FreeList {
+ public:
+ nsTArray<void*> mEntries;
+ size_t mEntrySize;
+ size_t mEntriesEverAllocated;
+
+ FreeList() : mEntrySize(0), mEntriesEverAllocated(0) {}
+
+ size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const {
+ return mEntries.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ }
+ };
+
+ FreeList mFreeLists[ObjectIdCount];
+ mozilla::ArenaAllocator<ArenaSize, 8> mPool;
+};
+
+#endif
diff --git a/layout/base/nsPresArenaObjectList.h b/layout/base/nsPresArenaObjectList.h
new file mode 100644
index 0000000000..54c319d36b
--- /dev/null
+++ b/layout/base/nsPresArenaObjectList.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/. */
+
+/* a list of all types that can be allocated in an nsPresArena, for
+ preprocessing */
+
+// These are objects that can be stored in the pres arena
+//
+// FIXME(emilio): Including abstract frame classes in the arena is a bit
+// wasteful, but it simplifies the allocation code.
+#define FRAME_ID(classname, ...) PRES_ARENA_OBJECT(classname)
+#define ABSTRACT_FRAME_ID(classname) PRES_ARENA_OBJECT(classname)
+#include "mozilla/FrameIdList.h"
+#undef FRAME_ID
+#undef ABSTRACT_FRAME_ID
+PRES_ARENA_OBJECT(nsLineBox)
+PRES_ARENA_OBJECT(nsFrameList)
+PRES_ARENA_OBJECT(CustomCounterStyle)
+PRES_ARENA_OBJECT(DependentBuiltinCounterStyle)
+PRES_ARENA_OBJECT(nsCallbackEventRequest)
+PRES_ARENA_OBJECT(nsIntervalSet_Interval)
+PRES_ARENA_OBJECT(CellData)
+PRES_ARENA_OBJECT(BCCellData)
diff --git a/layout/base/nsPresContext.cpp b/layout/base/nsPresContext.cpp
new file mode 100644
index 0000000000..c1b03aebf4
--- /dev/null
+++ b/layout/base/nsPresContext.cpp
@@ -0,0 +1,3137 @@
+/* -*- 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 presentation of a document, part 1 */
+
+#include "nsPresContext.h"
+#include "nsPresContextInlines.h"
+
+#include "mozilla/ArrayUtils.h"
+#if defined(MOZ_WIDGET_ANDROID)
+# include "mozilla/AsyncEventDispatcher.h"
+#endif
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Encoding.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+
+#include "base/basictypes.h"
+#include "nsCRT.h"
+#include "nsCOMPtr.h"
+#include "nsCSSFrameConstructor.h"
+#include "nsDocShell.h"
+#include "nsIConsoleService.h"
+#include "nsIDocumentViewer.h"
+#include "nsPIDOMWindow.h"
+#include "mozilla/ServoStyleSet.h"
+#include "mozilla/MediaFeatureChange.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIPrintSettings.h"
+#include "nsLanguageAtomService.h"
+#include "mozilla/LookAndFeel.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsHTMLDocument.h"
+#include "nsIWeakReferenceUtils.h"
+#include "nsThreadUtils.h"
+#include "nsLayoutUtils.h"
+#include "nsViewManager.h"
+#include "mozilla/RestyleManager.h"
+#include "gfxPlatform.h"
+#include "nsFontFaceLoader.h"
+#include "mozilla/AnimationEventDispatcher.h"
+#include "mozilla/EffectCompositor.h"
+#include "mozilla/EventListenerManager.h"
+#include "prenv.h"
+#include "nsTransitionManager.h"
+#include "nsAnimationManager.h"
+#include "CounterStyleManager.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/dom/Element.h"
+#include "nsIMessageManager.h"
+#include "mozilla/dom/HTMLBodyElement.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/SMILAnimationController.h"
+#include "mozilla/css/ImageLoader.h"
+#include "mozilla/dom/PBrowserParent.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/BrowserParent.h"
+#include "mozilla/dom/FontFaceSet.h"
+#include "mozilla/StaticPresData.h"
+#include "nsRefreshDriver.h"
+#include "LayerUserData.h"
+#include "mozilla/dom/NotifyPaintEvent.h"
+#include "nsFontCache.h"
+#include "nsFrameLoader.h"
+#include "nsContentUtils.h"
+#include "nsPIWindowRoot.h"
+#include "mozilla/Preferences.h"
+#include "gfxTextRun.h"
+#include "nsFontFaceUtils.h"
+#include "COLRFonts.h"
+#include "mozilla/ContentBlockingAllowList.h"
+#include "mozilla/GlobalStyleSheetCache.h"
+#include "mozilla/ServoBindings.h"
+#include "mozilla/StaticPrefs_bidi.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_zoom.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TimelineManager.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/PerformanceMainThread.h"
+#include "mozilla/dom/PerformanceTiming.h"
+#include "mozilla/dom/PerformancePaintTiming.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "MobileViewportManager.h"
+#include "mozilla/dom/ImageTracker.h"
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/DocAccessible.h"
+#endif
+
+// Needed for Start/Stop of Image Animation
+#include "imgIContainer.h"
+#include "nsIImageLoadingContent.h"
+
+#include "nsBidiUtils.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/dom/URL.h"
+#include "mozilla/ServoCSSParser.h"
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+
+/**
+ * Layer UserData for ContainerLayers that want to be notified
+ * of local invalidations of them and their descendant layers.
+ * Pass a callback to ComputeDifferences to have these called.
+ */
+class ContainerLayerPresContext : public LayerUserData {
+ public:
+ nsPresContext* mPresContext;
+};
+
+bool nsPresContext::IsDOMPaintEventPending() {
+ if (!mTransactions.IsEmpty()) {
+ return true;
+ }
+
+ nsRootPresContext* drpc = GetRootPresContext();
+ if (drpc && drpc->mRefreshDriver->ViewManagerFlushIsPending()) {
+ // Since we're promising that there will be a MozAfterPaint event
+ // fired, we record an empty invalidation in case display list
+ // invalidation doesn't invalidate anything further.
+ NotifyInvalidation(drpc->mRefreshDriver->LastTransactionId().Next(),
+ nsRect(0, 0, 0, 0));
+ return true;
+ }
+ return false;
+}
+
+struct WeakRunnableMethod : Runnable {
+ using Method = void (nsPresContext::*)();
+
+ WeakRunnableMethod(const char* aName, nsPresContext* aPc, Method aMethod)
+ : Runnable(aName), mPresContext(aPc), mMethod(aMethod) {}
+
+ NS_IMETHOD Run() override {
+ if (nsPresContext* pc = mPresContext.get()) {
+ (pc->*mMethod)();
+ }
+ return NS_OK;
+ }
+
+ private:
+ WeakPtr<nsPresContext> mPresContext;
+ Method mMethod;
+};
+
+// When forcing a font-info-update reflow from style, we don't need to reframe,
+// but we'll need to restyle to pick up updated font metrics. In order to avoid
+// synchronously having to deal with multiple restyles, we use an early refresh
+// driver runner, which should prevent flashing for users.
+//
+// We might do a bit of extra work if the page flushes layout between the
+// restyle and when this happens, which is a bit unfortunate, but not worse than
+// what we used to do...
+//
+// A better solution would be to be able to synchronously initialize font
+// information from style worker threads, perhaps...
+void nsPresContext::ForceReflowForFontInfoUpdateFromStyle() {
+ if (mPendingFontInfoUpdateReflowFromStyle) {
+ return;
+ }
+
+ mPendingFontInfoUpdateReflowFromStyle = true;
+ nsCOMPtr<nsIRunnable> ev = new WeakRunnableMethod(
+ "nsPresContext::DoForceReflowForFontInfoUpdateFromStyle", this,
+ &nsPresContext::DoForceReflowForFontInfoUpdateFromStyle);
+ RefreshDriver()->AddEarlyRunner(ev);
+}
+
+void nsPresContext::DoForceReflowForFontInfoUpdateFromStyle() {
+ mPendingFontInfoUpdateReflowFromStyle = false;
+ ForceReflowForFontInfoUpdate(false);
+}
+
+void nsPresContext::ForceReflowForFontInfoUpdate(bool aNeedsReframe) {
+ // In the case of a static-clone document used for printing or print-preview,
+ // this is undesirable because the nsPrintJob is holding weak refs to frames
+ // that will get blown away unexpectedly by this reconstruction. So the
+ // prescontext for a print/preview doc ignores the font-list update.
+ //
+ // This means the print document may still be using cached fonts that are no
+ // longer present in the font list, but that should be safe given that all the
+ // required font instances have already been created, so it won't be depending
+ // on access to the font-list entries.
+ //
+ // XXX Actually, I think it's probably a bad idea to do *any* restyling of
+ // print documents in response to pref changes. We could be in the middle
+ // of printing the document, and reflowing all the frames might cause some
+ // kind of unwanted mid-document discontinuity.
+ if (IsPrintingOrPrintPreview()) {
+ return;
+ }
+
+ // If there's a user font set, discard any src:local() faces it may have
+ // loaded because their font entries may no longer be valid.
+ if (auto* fonts = Document()->GetFonts()) {
+ fonts->GetImpl()->ForgetLocalFaces();
+ }
+
+ FlushFontCache();
+
+ if (!mPresShell) {
+ // RebuildAllStyleData won't do anything without mPresShell.
+ return;
+ }
+
+ nsChangeHint changeHint =
+ aNeedsReframe ? nsChangeHint_ReconstructFrame : NS_STYLE_HINT_REFLOW;
+
+ // We also need to trigger restyling for ex/ch units changes to take effect,
+ // if needed.
+ auto restyleHint = StyleSet()->UsesFontMetrics()
+ ? RestyleHint::RecascadeSubtree()
+ : RestyleHint{0};
+
+ RebuildAllStyleData(changeHint, restyleHint);
+}
+
+static bool IsVisualCharset(NotNull<const Encoding*> aCharset) {
+ return aCharset == ISO_8859_8_ENCODING;
+}
+
+nsPresContext::nsPresContext(dom::Document* aDocument, nsPresContextType aType)
+ : mPresShell(nullptr),
+ mDocument(aDocument),
+ mMedium(aType == eContext_Galley ? nsGkAtoms::screen : nsGkAtoms::print),
+ mTextZoom(1.0),
+ mFullZoom(1.0),
+ mLastFontInflationScreenSize(gfxSize(-1.0, -1.0)),
+ mCurAppUnitsPerDevPixel(0),
+ mDynamicToolbarMaxHeight(0),
+ mDynamicToolbarHeight(0),
+ mPageSize(-1, -1),
+ mPageScale(0.0),
+ mPPScale(1.0f),
+ mViewportScrollOverrideElement(nullptr),
+ mElementsRestyled(0),
+ mFramesConstructed(0),
+ mFramesReflowed(0),
+ mAnimationTriggeredRestyles(0),
+ mInterruptChecksToSkip(0),
+ mNextFrameRateMultiplier(0),
+ mMeasuredTicksSinceLoading(0),
+ mViewportScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto),
+ // mImageAnimationMode is initialised below, in constructor body
+ mImageAnimationModePref(imgIContainer::kNormalAnimMode),
+ mType(aType),
+ mInflationDisabledForShrinkWrap(false),
+ mInteractionTimeEnabled(true),
+ mHasPendingInterrupt(false),
+ mHasEverBuiltInvisibleText(false),
+ mPendingInterruptFromTest(false),
+ mInterruptsEnabled(false),
+ mDrawImageBackground(true), // always draw the background
+ mDrawColorBackground(true),
+ // mNeverAnimate is initialised below, in constructor body
+ mPaginated(aType != eContext_Galley),
+ mCanPaginatedScroll(false),
+ mDoScaledTwips(true),
+ mIsRootPaginatedDocument(false),
+ mPendingThemeChanged(false),
+ mPendingThemeChangeKind(0),
+ mPendingUIResolutionChanged(false),
+ mPendingFontInfoUpdateReflowFromStyle(false),
+ mIsGlyph(false),
+ mCounterStylesDirty(true),
+ mFontFeatureValuesDirty(true),
+ mFontPaletteValuesDirty(true),
+ mIsVisual(false),
+ mInRDMPane(false),
+ mHasWarnedAboutTooLargeDashedOrDottedRadius(false),
+ mQuirkSheetAdded(false),
+ mHadNonBlankPaint(false),
+ mHadFirstContentfulPaint(false),
+ mHadNonTickContentfulPaint(false),
+ mHadContentfulPaintComposite(false),
+ mNeedsToUpdateHiddenByContentVisibilityForAnimations(false),
+ mUserInputEventsAllowed(false),
+#ifdef DEBUG
+ mInitialized(false),
+#endif
+ mOverriddenOrEmbedderColorScheme(dom::PrefersColorSchemeOverride::None) {
+#ifdef DEBUG
+ PodZero(&mLayoutPhaseCount);
+#endif
+
+ if (!IsDynamic()) {
+ mImageAnimationMode = imgIContainer::kDontAnimMode;
+ mNeverAnimate = true;
+ } else {
+ mImageAnimationMode = imgIContainer::kNormalAnimMode;
+ mNeverAnimate = false;
+ }
+ NS_ASSERTION(mDocument, "Null document");
+
+ // if text perf logging enabled, init stats struct
+ if (MOZ_LOG_TEST(gfxPlatform::GetLog(eGfxLog_textperf), LogLevel::Warning)) {
+ mTextPerf = MakeUnique<gfxTextPerfMetrics>();
+ }
+
+ if (StaticPrefs::gfx_missing_fonts_notify()) {
+ mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
+ }
+
+ if (StaticPrefs::layout_dynamic_toolbar_max_height() > 0) {
+ // The pref for dynamic toolbar max height is only used in reftests so it's
+ // fine to set here.
+ mDynamicToolbarMaxHeight = StaticPrefs::layout_dynamic_toolbar_max_height();
+ }
+
+ UpdateFontVisibility();
+}
+
+static const char* gExactCallbackPrefs[] = {
+ "browser.active_color",
+ "browser.anchor_color",
+ "browser.visited_color",
+ "dom.meta-viewport.enabled",
+ "image.animation_mode",
+ "intl.accept_languages",
+ "layout.css.devPixelsPerPx",
+ "layout.css.dpi",
+ "layout.css.text-transform.uppercase-eszett.enabled",
+ "privacy.trackingprotection.enabled",
+ "ui.use_standins_for_native_colors",
+ nullptr,
+};
+
+static const char* gPrefixCallbackPrefs[] = {
+ "bidi.", "browser.display.", "browser.viewport.",
+ "font.", "gfx.font_rendering.", "layout.css.font-visibility.",
+ nullptr,
+};
+
+void nsPresContext::Destroy() {
+ if (mEventManager) {
+ // unclear if these are needed, but can't hurt
+ mEventManager->NotifyDestroyPresContext(this);
+ mEventManager->SetPresContext(nullptr);
+ mEventManager = nullptr;
+ }
+
+ if (mFontCache) {
+ mFontCache->Destroy();
+ mFontCache = nullptr;
+ }
+
+ // Unregister preference callbacks
+ Preferences::UnregisterPrefixCallbacks(nsPresContext::PreferenceChanged,
+ gPrefixCallbackPrefs, this);
+ Preferences::UnregisterCallbacks(nsPresContext::PreferenceChanged,
+ gExactCallbackPrefs, this);
+
+ mRefreshDriver = nullptr;
+ MOZ_ASSERT(mManagedPostRefreshObservers.IsEmpty());
+}
+
+nsPresContext::~nsPresContext() {
+ MOZ_ASSERT(!mPresShell, "Presshell forgot to clear our mPresShell pointer");
+ DetachPresShell();
+
+ Destroy();
+}
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPresContext)
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPresContext)
+NS_IMPL_CYCLE_COLLECTING_RELEASE_WITH_LAST_RELEASE(nsPresContext, LastRelease())
+
+void nsPresContext::LastRelease() {
+ if (mMissingFonts) {
+ mMissingFonts->Clear();
+ }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsPresContext)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsPresContext)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAnimationEventDispatcher);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocument);
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDeviceContext); // not xpcom
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEffectCompositor);
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEventManager);
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mLanguage); // an atom
+
+ // NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTheme); // a service
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrintSettings);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsPresContext)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mAnimationEventDispatcher);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocument);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDeviceContext); // worth bothering?
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mEffectCompositor);
+ // NS_RELEASE(tmp->mLanguage); // an atom
+ // NS_IMPL_CYCLE_COLLECTION_UNLINK(mTheme); // a service
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mPrintSettings);
+ NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_PTR
+
+ tmp->Destroy();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+bool nsPresContext::IsChrome() const {
+ return Document()->IsInChromeDocShell();
+}
+
+void nsPresContext::GetUserPreferences() {
+ if (!GetPresShell()) {
+ // No presshell means nothing to do here. We'll do this when we
+ // get a presshell.
+ return;
+ }
+
+ PreferenceSheet::EnsureInitialized();
+
+ Document()->SetMayNeedFontPrefsUpdate();
+
+ // * image animation
+ nsAutoCString animatePref;
+ Preferences::GetCString("image.animation_mode", animatePref);
+ if (animatePref.EqualsLiteral("normal"))
+ mImageAnimationModePref = imgIContainer::kNormalAnimMode;
+ else if (animatePref.EqualsLiteral("none"))
+ mImageAnimationModePref = imgIContainer::kDontAnimMode;
+ else if (animatePref.EqualsLiteral("once"))
+ mImageAnimationModePref = imgIContainer::kLoopOnceAnimMode;
+ else // dynamic change to invalid value should act like it does initially
+ mImageAnimationModePref = imgIContainer::kNormalAnimMode;
+
+ uint32_t bidiOptions = GetBidi();
+
+ SET_BIDI_OPTION_DIRECTION(bidiOptions, StaticPrefs::bidi_direction());
+ SET_BIDI_OPTION_TEXTTYPE(bidiOptions, StaticPrefs::bidi_texttype());
+ SET_BIDI_OPTION_NUMERAL(bidiOptions, StaticPrefs::bidi_numeral());
+
+ // We don't need to force reflow: either we are initializing a new
+ // prescontext or we are being called from PreferenceChanged()
+ // which triggers a reflow anyway.
+ SetBidi(bidiOptions);
+}
+
+void nsPresContext::InvalidatePaintedLayers() {
+ if (!mPresShell) {
+ return;
+ }
+ if (nsIFrame* rootFrame = mPresShell->GetRootFrame()) {
+ // FrameLayerBuilder caches invalidation-related values that depend on the
+ // appunits-per-dev-pixel ratio, so ensure that all PaintedLayer drawing
+ // is completely flushed.
+ rootFrame->InvalidateFrameSubtree();
+ }
+}
+
+void nsPresContext::AppUnitsPerDevPixelChanged() {
+ int32_t oldAppUnitsPerDevPixel = mCurAppUnitsPerDevPixel;
+
+ InvalidatePaintedLayers();
+
+ FlushFontCache();
+
+ MediaFeatureValuesChanged(
+ {RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_REFLOW,
+ MediaFeatureChangeReason::ResolutionChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+
+ mCurAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel();
+
+#ifdef ACCESSIBILITY
+ if (mCurAppUnitsPerDevPixel != oldAppUnitsPerDevPixel) {
+ if (nsAccessibilityService* accService = GetAccService()) {
+ accService->NotifyOfDevPixelRatioChange(mPresShell,
+ mCurAppUnitsPerDevPixel);
+ }
+ }
+#endif
+
+ // Recompute the size for vh units since it's changed by the dynamic toolbar
+ // max height which is stored in screen coord.
+ if (IsRootContentDocumentCrossProcess()) {
+ AdjustSizeForViewportUnits();
+ }
+
+ // nsSubDocumentFrame uses a AppUnitsPerDevPixel difference between parent and
+ // child document to determine if it needs to build a nsDisplayZoom item. So
+ // if we that changes then we need to invalidate the subdoc frame so that
+ // item gets created/removed.
+ if (mPresShell) {
+ if (nsIFrame* frame = mPresShell->GetRootFrame()) {
+ frame = nsLayoutUtils::GetCrossDocParentFrameInProcess(frame);
+ if (frame) {
+ int32_t parentAPD = frame->PresContext()->AppUnitsPerDevPixel();
+ if ((parentAPD == oldAppUnitsPerDevPixel) !=
+ (parentAPD == mCurAppUnitsPerDevPixel)) {
+ frame->InvalidateFrame();
+ }
+ }
+ }
+ }
+
+ // We would also have to look at all of our child subdocuments but the
+ // InvalidatePaintedLayers call above calls InvalidateFrameSubtree which
+ // would invalidate all subdocument frames already.
+}
+
+// static
+void nsPresContext::PreferenceChanged(const char* aPrefName, void* aSelf) {
+ static_cast<nsPresContext*>(aSelf)->PreferenceChanged(aPrefName);
+}
+
+void nsPresContext::PreferenceChanged(const char* aPrefName) {
+ if (!mPresShell) {
+ return;
+ }
+
+ nsDependentCString prefName(aPrefName);
+ if (prefName.EqualsLiteral("layout.css.dpi") ||
+ prefName.EqualsLiteral("layout.css.devPixelsPerPx")) {
+ int32_t oldAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel();
+ // We need to assume the DPI changes, since `mDeviceContext` is shared with
+ // other documents, and we'd need to save the return value of the first call
+ // for all of them.
+ Unused << mDeviceContext->CheckDPIChange();
+ OwningNonNull<mozilla::PresShell> presShell(*mPresShell);
+ // Re-fetch the view manager's window dimensions in case there's a
+ // deferred resize which hasn't affected our mVisibleArea yet
+ nscoord oldWidthAppUnits, oldHeightAppUnits;
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (!vm) {
+ return;
+ }
+ vm->GetWindowDimensions(&oldWidthAppUnits, &oldHeightAppUnits);
+ float oldWidthDevPixels = oldWidthAppUnits / oldAppUnitsPerDevPixel;
+ float oldHeightDevPixels = oldHeightAppUnits / oldAppUnitsPerDevPixel;
+
+ UIResolutionChangedInternal();
+
+ nscoord width = NSToCoordRound(oldWidthDevPixels * AppUnitsPerDevPixel());
+ nscoord height = NSToCoordRound(oldHeightDevPixels * AppUnitsPerDevPixel());
+ vm->SetWindowDimensions(width, height);
+ return;
+ }
+
+ if (StringBeginsWith(prefName, "browser.viewport."_ns) ||
+ StringBeginsWith(prefName, "font.size.inflation."_ns) ||
+ prefName.EqualsLiteral("dom.meta-viewport.enabled")) {
+ mPresShell->MaybeReflowForInflationScreenSizeChange();
+ }
+
+ auto changeHint = nsChangeHint{0};
+ auto restyleHint = RestyleHint{0};
+ // Changing any of these potentially changes the value of @media
+ // (prefers-contrast).
+ // The layout.css.prefers-contrast.enabled pref itself is not handled here,
+ // because that pref doesn't just affect the "live" value of the media query;
+ // it affects whether it is parsed at all.
+ if (prefName.EqualsLiteral("browser.display.document_color_use") ||
+ prefName.EqualsLiteral("browser.display.foreground_color") ||
+ prefName.EqualsLiteral("browser.display.background_color")) {
+ MediaFeatureValuesChanged({MediaFeatureChangeReason::PreferenceChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+ if (prefName.EqualsLiteral(GFX_MISSING_FONTS_NOTIFY_PREF)) {
+ if (StaticPrefs::gfx_missing_fonts_notify()) {
+ if (!mMissingFonts) {
+ mMissingFonts = MakeUnique<gfxMissingFontRecorder>();
+ // trigger reflow to detect missing fonts on the current page
+ changeHint |= NS_STYLE_HINT_REFLOW;
+ }
+ } else {
+ if (mMissingFonts) {
+ mMissingFonts->Clear();
+ }
+ mMissingFonts = nullptr;
+ }
+ }
+
+ if (StringBeginsWith(prefName, "font."_ns) ||
+ // Changes to font family preferences don't change anything in the
+ // computed style data, so the style system won't generate a reflow hint
+ // for us. We need to do that manually.
+ prefName.EqualsLiteral("intl.accept_languages") ||
+ // Changes to bidi prefs need to trigger a reflow (see bug 443629)
+ StringBeginsWith(prefName, "bidi."_ns) ||
+ // Changes to font_rendering prefs need to trigger a reflow
+ StringBeginsWith(prefName, "gfx.font_rendering."_ns)) {
+ changeHint |= NS_STYLE_HINT_REFLOW;
+ if (StyleSet()->UsesFontMetrics()) {
+ restyleHint |= RestyleHint::RecascadeSubtree();
+ }
+ }
+
+ if (prefName.EqualsLiteral(
+ "layout.css.text-transform.uppercase-eszett.enabled")) {
+ changeHint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ if (PreferenceSheet::AffectedByPref(prefName)) {
+ restyleHint |= RestyleHint::RestyleSubtree();
+ PreferenceSheet::Refresh();
+ }
+
+ // Same, this just frees a bunch of memory.
+ StaticPresData::Get()->InvalidateFontPrefs();
+ Document()->SetMayNeedFontPrefsUpdate();
+
+ // Initialize our state from the user preferences.
+ GetUserPreferences();
+
+ FlushFontCache();
+ if (UpdateFontVisibility()) {
+ changeHint |= NS_STYLE_HINT_REFLOW;
+ }
+
+ // Preferences require rerunning selector matching because we rebuild
+ // the pref style sheet for some preference changes.
+ if (changeHint || restyleHint) {
+ RebuildAllStyleData(changeHint, restyleHint);
+ }
+
+ InvalidatePaintedLayers();
+}
+
+nsresult nsPresContext::Init(nsDeviceContext* aDeviceContext) {
+ NS_ASSERTION(!mInitialized, "attempt to reinit pres context");
+ NS_ENSURE_ARG(aDeviceContext);
+
+ mDeviceContext = aDeviceContext;
+
+ // In certain rare cases (such as changing page mode), we tear down layout
+ // state and re-initialize a new prescontext for a document. Given that we
+ // hang style state off the DOM, we detect that re-initialization case and
+ // lazily drop the servo data. We don't do this eagerly during layout teardown
+ // because that would incur an extra whole-tree traversal that's unnecessary
+ // most of the time.
+ //
+ // FIXME(emilio): I'm pretty sure this doesn't happen after bug 1414999.
+ Element* root = mDocument->GetRootElement();
+ if (root && root->HasServoData()) {
+ RestyleManager::ClearServoDataFromSubtree(root);
+ }
+
+ if (mDeviceContext->SetFullZoom(mFullZoom)) {
+ FlushFontCache();
+ }
+ mCurAppUnitsPerDevPixel = mDeviceContext->AppUnitsPerDevPixel();
+
+ mEventManager = new mozilla::EventStateManager();
+
+ mAnimationEventDispatcher = new mozilla::AnimationEventDispatcher(this);
+ mEffectCompositor = new mozilla::EffectCompositor(this);
+ mTransitionManager = MakeUnique<nsTransitionManager>(this);
+ mAnimationManager = MakeUnique<nsAnimationManager>(this);
+ mTimelineManager = MakeUnique<mozilla::TimelineManager>(this);
+
+ if (mDocument->GetDisplayDocument()) {
+ NS_ASSERTION(mDocument->GetDisplayDocument()->GetPresContext(),
+ "Why are we being initialized?");
+ mRefreshDriver =
+ mDocument->GetDisplayDocument()->GetPresContext()->RefreshDriver();
+ } else {
+ dom::Document* parent = mDocument->GetInProcessParentDocument();
+ // Unfortunately, sometimes |parent| here has no presshell because
+ // printing screws up things. Assert that in other cases it does,
+ // but whenever the shell is null just fall back on using our own
+ // refresh driver.
+ NS_ASSERTION(
+ !parent || mDocument->IsStaticDocument() || parent->GetPresShell(),
+ "How did we end up with a presshell if our parent doesn't "
+ "have one?");
+ if (parent && parent->GetPresContext()) {
+ // XXX the document can change in AttachPresShell, does this work?
+ dom::BrowsingContext* browsingContext = mDocument->GetBrowsingContext();
+ if (browsingContext && !browsingContext->IsTop()) {
+ Element* containingElement = mDocument->GetEmbedderElement();
+ if (!containingElement->IsXULElement() ||
+ !containingElement->HasAttr(nsGkAtoms::forceOwnRefreshDriver)) {
+ mRefreshDriver = parent->GetPresContext()->RefreshDriver();
+ }
+ }
+ }
+
+ if (!mRefreshDriver) {
+ mRefreshDriver = new nsRefreshDriver(this);
+ }
+ }
+
+ // Register callbacks so we're notified when the preferences change
+ Preferences::RegisterPrefixCallbacks(nsPresContext::PreferenceChanged,
+ gPrefixCallbackPrefs, this);
+ Preferences::RegisterCallbacks(nsPresContext::PreferenceChanged,
+ gExactCallbackPrefs, this);
+
+ nsresult rv = mEventManager->Init();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mEventManager->SetPresContext(this);
+
+#if defined(MOZ_WIDGET_ANDROID)
+ if (IsRootContentDocumentCrossProcess() &&
+ MOZ_LIKELY(
+ !Preferences::HasUserValue("layout.dynamic-toolbar-max-height"))) {
+ if (BrowserChild* browserChild =
+ BrowserChild::GetFrom(mDocument->GetDocShell())) {
+ mDynamicToolbarMaxHeight = browserChild->GetDynamicToolbarMaxHeight();
+ mDynamicToolbarHeight = mDynamicToolbarMaxHeight;
+ }
+ }
+#endif
+
+#ifdef DEBUG
+ mInitialized = true;
+#endif
+
+ return NS_OK;
+}
+
+bool nsPresContext::UpdateFontVisibility() {
+ FontVisibility oldValue = mFontVisibility;
+
+ /*
+ * Expected behavior in order of precedence:
+ * 1 Chrome Rules give User Level (3)
+ * 2 RFP gives Highest Level (1 aka Base)
+ * 3 An RFPTarget of Base gives Base Level (1)
+ * 4 An RFPTarget of LangPack gives LangPack Level (2)
+ * 5 The value of the Standard Font Visibility Pref
+ *
+ * If the ETP toggle is disabled (aka
+ * ContentBlockingAllowList::Check is true), it will only override 3-5,
+ * not rules 1 or 2.
+ */
+
+ // Rule 1: Allow all font access for privileged contexts, including
+ // chrome and devtools contexts.
+ if (Document()->ChromeRulesEnabled()) {
+ mFontVisibility = FontVisibility::User;
+ return mFontVisibility != oldValue;
+ }
+
+ // Is this a private browsing context?
+ bool isPrivate = false;
+ if (nsCOMPtr<nsILoadContext> loadContext = mDocument->GetLoadContext()) {
+ isPrivate = loadContext->UsePrivateBrowsing();
+ }
+
+ int32_t level;
+ // Rule 3
+ if (mDocument->ShouldResistFingerprinting(
+ RFPTarget::FontVisibilityBaseSystem)) {
+ // Rule 2: Check RFP pref
+ // This is inside Rule 3 in case this document is exempted from RFP.
+ // But if it is not exempted, and RFP is enabled, we return immediately
+ // to prevent the override below from occurring.
+ if (nsRFPService::IsRFPPrefEnabled(isPrivate)) {
+ mFontVisibility = FontVisibility::Base;
+ return mFontVisibility != oldValue;
+ }
+
+ level = int32_t(FontVisibility::Base);
+ }
+ // Rule 4
+ else if (mDocument->ShouldResistFingerprinting(
+ RFPTarget::FontVisibilityLangPack)) {
+ level = int32_t(FontVisibility::LangPack);
+ }
+ // Rule 5
+ else {
+ level = StaticPrefs::layout_css_font_visibility();
+ }
+
+ // Override Rules 3-5 Only: Determine if the user has exempted the
+ // domain from tracking protections, if so, use the default value.
+ if (level != StaticPrefs::layout_css_font_visibility() &&
+ ContentBlockingAllowList::Check(mDocument->CookieJarSettings())) {
+ level = StaticPrefs::layout_css_font_visibility();
+ }
+
+ // Clamp result to the valid range of levels.
+ level = std::max(std::min(level, int32_t(FontVisibility::User)),
+ int32_t(FontVisibility::Base));
+
+ mFontVisibility = FontVisibility(level);
+ return mFontVisibility != oldValue;
+}
+
+void nsPresContext::ReportBlockedFontFamilyName(const nsCString& aFamily,
+ FontVisibility aVisibility) {
+ if (!mBlockedFonts.EnsureInserted(aFamily)) {
+ return;
+ }
+ nsAutoString msg;
+ msg.AppendPrintf(
+ "Request for font \"%s\" blocked at visibility level %d (requires %d)\n",
+ aFamily.get(), int(GetFontVisibility()), int(aVisibility));
+ nsContentUtils::ReportToConsoleNonLocalized(msg, nsIScriptError::warningFlag,
+ "Security"_ns, mDocument);
+}
+
+void nsPresContext::ReportBlockedFontFamily(const fontlist::Family& aFamily) {
+ auto* fontList = gfxPlatformFontList::PlatformFontList()->SharedFontList();
+ const nsCString& name = aFamily.DisplayName().AsString(fontList);
+ ReportBlockedFontFamilyName(name, aFamily.Visibility());
+}
+
+void nsPresContext::ReportBlockedFontFamily(const gfxFontFamily& aFamily) {
+ ReportBlockedFontFamilyName(aFamily.Name(), aFamily.Visibility());
+}
+
+void nsPresContext::InitFontCache() {
+ if (!mFontCache) {
+ mFontCache = new nsFontCache();
+ mFontCache->Init(this);
+ }
+}
+
+void nsPresContext::UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet) {
+ if (mFontCache) {
+ mFontCache->UpdateUserFonts(aUserFontSet);
+ }
+}
+
+already_AddRefed<nsFontMetrics> nsPresContext::GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams) {
+ InitFontCache();
+ return mFontCache->GetMetricsFor(aFont, aParams);
+}
+
+nsresult nsPresContext::FlushFontCache() {
+ if (mFontCache) {
+ mFontCache->Flush();
+ }
+ return NS_OK;
+}
+
+nsresult nsPresContext::FontMetricsDeleted(const nsFontMetrics* aFontMetrics) {
+ if (mFontCache) {
+ mFontCache->FontMetricsDeleted(aFontMetrics);
+ }
+ return NS_OK;
+}
+
+// Note: We don't hold a reference on the shell; it has a reference to
+// us
+void nsPresContext::AttachPresShell(mozilla::PresShell* aPresShell) {
+ MOZ_ASSERT(!mPresShell);
+ mPresShell = aPresShell;
+
+ mRestyleManager = MakeUnique<mozilla::RestyleManager>(this);
+
+ // Since CounterStyleManager is also the name of a method of
+ // nsPresContext, it is necessary to prefix the class with the mozilla
+ // namespace here.
+ mCounterStyleManager = new mozilla::CounterStyleManager(this);
+
+ dom::Document* doc = mPresShell->GetDocument();
+ MOZ_ASSERT(doc);
+ // Have to update PresContext's mDocument before calling any other methods.
+ mDocument = doc;
+
+ LookAndFeel::HandleGlobalThemeChange();
+
+ // Initialize our state from the user preferences, now that we
+ // have a presshell, and hence a document.
+ GetUserPreferences();
+
+ EnsureTheme();
+
+ nsIURI* docURI = doc->GetDocumentURI();
+
+ if (IsDynamic() && docURI) {
+ if (!docURI->SchemeIs("chrome") && !docURI->SchemeIs("resource"))
+ mImageAnimationMode = mImageAnimationModePref;
+ else
+ mImageAnimationMode = imgIContainer::kNormalAnimMode;
+ }
+
+ UpdateCharSet(doc->GetDocumentCharacterSet());
+}
+
+Maybe<ColorScheme> nsPresContext::GetOverriddenOrEmbedderColorScheme() const {
+ if (Medium() == nsGkAtoms::print) {
+ return Some(ColorScheme::Light);
+ }
+
+ switch (mOverriddenOrEmbedderColorScheme) {
+ case dom::PrefersColorSchemeOverride::Dark:
+ return Some(ColorScheme::Dark);
+ case dom::PrefersColorSchemeOverride::Light:
+ return Some(ColorScheme::Light);
+ case dom::PrefersColorSchemeOverride::None:
+ case dom::PrefersColorSchemeOverride::EndGuard_:
+ break;
+ }
+
+ return Nothing();
+}
+
+void nsPresContext::SetColorSchemeOverride(
+ PrefersColorSchemeOverride aOverride) {
+ auto oldScheme = mDocument->PreferredColorScheme();
+
+ mOverriddenOrEmbedderColorScheme = aOverride;
+
+ if (mDocument->PreferredColorScheme() != oldScheme) {
+ MediaFeatureValuesChanged(
+ MediaFeatureChange::ForPreferredColorSchemeChange(),
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+}
+
+void nsPresContext::RecomputeBrowsingContextDependentData() {
+ MOZ_ASSERT(mDocument);
+ dom::Document* doc = mDocument;
+ // Resource documents inherit all this state from their display document.
+ while (dom::Document* outer = doc->GetDisplayDocument()) {
+ doc = outer;
+ }
+ auto* browsingContext = doc->GetBrowsingContext();
+ if (!browsingContext) {
+ // This can legitimately happen for e.g. SVG images. Those just get scaled
+ // as a result of the zoom on the embedder document so it doesn't really
+ // matter... Medium also doesn't affect those.
+ return;
+ }
+ if (!IsPrintingOrPrintPreview()) {
+ auto systemZoom = LookAndFeel::SystemZoomSettings();
+ SetFullZoom(browsingContext->FullZoom() * systemZoom.mFullZoom);
+ SetTextZoom(browsingContext->TextZoom() * systemZoom.mTextZoom);
+ SetOverrideDPPX(browsingContext->OverrideDPPX());
+ }
+
+ auto* top = browsingContext->Top();
+ SetColorSchemeOverride([&] {
+ auto overriden = top->PrefersColorSchemeOverride();
+ if (overriden != PrefersColorSchemeOverride::None) {
+ return overriden;
+ }
+ if (!StaticPrefs::
+ layout_css_iframe_embedder_prefers_color_scheme_content_enabled()) {
+ return top->GetEmbedderColorSchemes().mPreferred;
+ }
+ return browsingContext->GetEmbedderColorSchemes().mPreferred;
+ }());
+
+ SetInRDMPane(top->GetInRDMPane());
+
+ if (doc == mDocument) {
+ // Medium doesn't apply to resource documents, etc.
+ RefPtr<nsAtom> mediumToEmulate;
+ if (MOZ_UNLIKELY(!top->GetMediumOverride().IsEmpty())) {
+ nsAutoString lower;
+ nsContentUtils::ASCIIToLower(top->GetMediumOverride(), lower);
+ mediumToEmulate = NS_Atomize(lower);
+ }
+ EmulateMedium(mediumToEmulate);
+ }
+
+ mDocument->EnumerateExternalResources([](dom::Document& aSubResource) {
+ if (nsPresContext* subResourcePc = aSubResource.GetPresContext()) {
+ subResourcePc->RecomputeBrowsingContextDependentData();
+ }
+ return CallState::Continue;
+ });
+}
+
+void nsPresContext::DetachPresShell() {
+ // The counter style manager's destructor needs to deallocate with the
+ // presshell arena. Disconnect it before nulling out the shell.
+ //
+ // XXXbholley: Given recent refactorings, it probably makes more sense to
+ // just null our mPresShell at the bottom of this function. I'm leaving it
+ // this way to preserve the old ordering, but I doubt anything would break.
+ if (mCounterStyleManager) {
+ mCounterStyleManager->Disconnect();
+ mCounterStyleManager = nullptr;
+ }
+
+ mPresShell = nullptr;
+
+ CancelManagedPostRefreshObservers();
+
+ if (mAnimationEventDispatcher) {
+ mAnimationEventDispatcher->Disconnect();
+ mAnimationEventDispatcher = nullptr;
+ }
+ if (mEffectCompositor) {
+ mEffectCompositor->Disconnect();
+ mEffectCompositor = nullptr;
+ }
+ if (mTransitionManager) {
+ mTransitionManager->Disconnect();
+ mTransitionManager = nullptr;
+ }
+ if (mAnimationManager) {
+ mAnimationManager->Disconnect();
+ mAnimationManager = nullptr;
+ }
+ if (mTimelineManager) {
+ mTimelineManager->Disconnect();
+ mTimelineManager = nullptr;
+ }
+ if (mRestyleManager) {
+ mRestyleManager->Disconnect();
+ mRestyleManager = nullptr;
+ }
+ if (mRefreshDriver && mRefreshDriver->GetPresContext() == this) {
+ mRefreshDriver->Disconnect();
+ // Can't null out the refresh driver here.
+ }
+}
+
+struct QueryContainerState {
+ nsSize mSize;
+ WritingMode mWm;
+ StyleContainerType mType;
+
+ nscoord GetInlineSize() const { return LogicalSize(mWm, mSize).ISize(mWm); }
+
+ bool Changed(const QueryContainerState& aNewState) {
+ if (mType != aNewState.mType) {
+ return true;
+ }
+ switch (mType) {
+ case StyleContainerType::Normal:
+ break;
+ case StyleContainerType::Size:
+ return mSize != aNewState.mSize;
+ case StyleContainerType::InlineSize:
+ return GetInlineSize() != aNewState.GetInlineSize();
+ }
+ return false;
+ }
+};
+NS_DECLARE_FRAME_PROPERTY_DELETABLE(ContainerState, QueryContainerState);
+
+void nsPresContext::RegisterContainerQueryFrame(nsIFrame* aFrame) {
+ mContainerQueryFrames.Add(aFrame);
+}
+
+void nsPresContext::UnregisterContainerQueryFrame(nsIFrame* aFrame) {
+ mContainerQueryFrames.Remove(aFrame);
+}
+
+void nsPresContext::FinishedContainerQueryUpdate() {
+ mUpdatedContainerQueryContents.Clear();
+}
+
+bool nsPresContext::UpdateContainerQueryStyles() {
+ if (mContainerQueryFrames.IsEmpty()) {
+ return false;
+ }
+
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Container Query Styles Update", LAYOUT);
+ AUTO_PROFILER_MARKER_TEXT("UpdateContainerQueryStyles", LAYOUT, {}, ""_ns);
+
+ PresShell()->DoFlushLayout(/* aInterruptible = */ false);
+
+ AutoTArray<nsIFrame*, 8> framesToUpdate;
+
+ bool anyChanged = false;
+ for (nsIFrame* frame : mContainerQueryFrames.IterFromShallowest()) {
+ MOZ_ASSERT(frame->IsPrimaryFrame());
+
+ auto type = frame->StyleDisplay()->mContainerType;
+ MOZ_ASSERT(type != StyleContainerType::Normal,
+ "Non-container frames shouldn't be in this set");
+
+ const QueryContainerState newState{frame->GetSize(),
+ frame->GetWritingMode(), type};
+ QueryContainerState* oldState = frame->GetProperty(ContainerState());
+
+ const bool changed = !oldState || oldState->Changed(newState);
+
+ // Make sure to update the state regardless. It's cheap and it keeps tracks
+ // of both axes correctly even if only one axis is contained.
+ if (oldState) {
+ *oldState = newState;
+ } else {
+ frame->SetProperty(ContainerState(), new QueryContainerState(newState));
+ }
+
+ if (!changed) {
+ continue;
+ }
+
+ const bool updatingAncestor = [&] {
+ for (nsIFrame* f : framesToUpdate) {
+ if (nsLayoutUtils::IsProperAncestorFrame(f, frame)) {
+ return true;
+ }
+ }
+ return false;
+ }();
+
+ if (updatingAncestor) {
+ // We're going to update an ancestor container of this frame already,
+ // avoid updating this one too until all our ancestor containers are
+ // updated.
+ continue;
+ }
+
+ // To prevent unstable layout, only update once per-element per-flush.
+ if (NS_WARN_IF(!mUpdatedContainerQueryContents.EnsureInserted(
+ frame->GetContent()))) {
+ continue;
+ }
+
+ framesToUpdate.AppendElement(frame);
+
+ // TODO(emilio): More fine-grained invalidation rather than invalidating the
+ // whole subtree, probably!
+ RestyleManager()->PostRestyleEvent(frame->GetContent()->AsElement(),
+ RestyleHint::RestyleSubtree(),
+ nsChangeHint(0));
+ anyChanged = true;
+ }
+ return anyChanged;
+}
+
+void nsPresContext::DocumentCharSetChanged(NotNull<const Encoding*> aCharSet) {
+ UpdateCharSet(aCharSet);
+ FlushFontCache();
+
+ // If a document contains one or more <script> elements, frame construction
+ // might happen earlier than the UpdateCharSet(), so we need to restyle
+ // descendants to make their style data up-to-date.
+ //
+ // FIXME(emilio): Revisit whether this is true after bug 1438911.
+ RebuildAllStyleData(NS_STYLE_HINT_REFLOW, RestyleHint::RecascadeSubtree());
+}
+
+void nsPresContext::UpdateCharSet(NotNull<const Encoding*> aCharSet) {
+ switch (GET_BIDI_OPTION_TEXTTYPE(GetBidi())) {
+ case IBMBIDI_TEXTTYPE_LOGICAL:
+ SetVisualMode(false);
+ break;
+
+ case IBMBIDI_TEXTTYPE_VISUAL:
+ SetVisualMode(true);
+ break;
+
+ case IBMBIDI_TEXTTYPE_CHARSET:
+ default:
+ SetVisualMode(IsVisualCharset(aCharSet));
+ }
+}
+
+nsPresContext* nsPresContext::GetParentPresContext() const {
+ mozilla::PresShell* presShell = GetPresShell();
+ if (presShell) {
+ nsViewManager* viewManager = presShell->GetViewManager();
+ if (viewManager) {
+ nsView* view = viewManager->GetRootView();
+ if (view) {
+ view = view->GetParent(); // anonymous inner view
+ if (view) {
+ view = view->GetParent(); // subdocumentframe's view
+ if (view) {
+ nsIFrame* f = view->GetFrame();
+ if (f) {
+ return f->PresContext();
+ }
+ }
+ }
+ }
+ }
+ }
+ return nullptr;
+}
+
+nsPresContext* nsPresContext::GetInProcessRootContentDocumentPresContext() {
+ if (IsChrome()) return nullptr;
+ nsPresContext* pc = this;
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent || parent->IsChrome()) return pc;
+ pc = parent;
+ }
+}
+
+nsIWidget* nsPresContext::GetNearestWidget(nsPoint* aOffset) {
+ NS_ENSURE_TRUE(mPresShell, nullptr);
+ nsViewManager* vm = mPresShell->GetViewManager();
+ NS_ENSURE_TRUE(vm, nullptr);
+ nsView* rootView = vm->GetRootView();
+ NS_ENSURE_TRUE(rootView, nullptr);
+ return rootView->GetNearestWidget(aOffset);
+}
+
+nsIWidget* nsPresContext::GetRootWidget() const {
+ NS_ENSURE_TRUE(mPresShell, nullptr);
+ nsViewManager* vm = mPresShell->GetViewManager();
+ if (!vm) {
+ return nullptr;
+ }
+ return vm->GetRootWidget();
+}
+
+// We may want to replace this with something faster, maybe caching the root
+// prescontext
+nsRootPresContext* nsPresContext::GetRootPresContext() const {
+ nsPresContext* pc = const_cast<nsPresContext*>(this);
+ for (;;) {
+ nsPresContext* parent = pc->GetParentPresContext();
+ if (!parent) break;
+ pc = parent;
+ }
+ return pc->IsRoot() ? static_cast<nsRootPresContext*>(pc) : nullptr;
+}
+
+bool nsPresContext::UserInputEventsAllowed() {
+ MOZ_ASSERT(IsRoot());
+ if (mUserInputEventsAllowed) {
+ return true;
+ }
+
+ // Special document
+ if (Document()->IsEverInitialDocument()) {
+ return true;
+ }
+
+ if (mRefreshDriver->IsThrottled()) {
+ MOZ_ASSERT(!mPresShell->IsVisible());
+ // This implies that the BC is not visibile and users can't
+ // interact with it, so we are okay with handling user inputs here.
+ return true;
+ }
+
+ if (mMeasuredTicksSinceLoading <
+ StaticPrefs::dom_input_events_security_minNumTicks()) {
+ return false;
+ }
+
+ if (!StaticPrefs::dom_input_events_security_minTimeElapsedInMS()) {
+ return true;
+ }
+
+ dom::Document* doc = Document();
+
+ MOZ_ASSERT_IF(StaticPrefs::dom_input_events_security_minNumTicks(),
+ doc->GetReadyStateEnum() >= Document::READYSTATE_LOADING);
+
+ TimeStamp loadingOrRestoredFromBFCacheTime =
+ doc->GetLoadingOrRestoredFromBFCacheTimeStamp();
+ MOZ_ASSERT(!loadingOrRestoredFromBFCacheTime.IsNull());
+
+ TimeDuration elapsed = TimeStamp::Now() - loadingOrRestoredFromBFCacheTime;
+ if (elapsed.ToMilliseconds() >=
+ StaticPrefs::dom_input_events_security_minTimeElapsedInMS()) {
+ mUserInputEventsAllowed = true;
+ return true;
+ }
+
+ return false;
+}
+
+void nsPresContext::MaybeIncreaseMeasuredTicksSinceLoading() {
+ MOZ_ASSERT(IsRoot());
+ if (mMeasuredTicksSinceLoading >=
+ StaticPrefs::dom_input_events_security_minNumTicks()) {
+ return;
+ }
+
+ // We consider READYSTATE_LOADING is the point when the page
+ // becomes interactive
+ if (Document()->GetReadyStateEnum() >= Document::READYSTATE_LOADING ||
+ Document()->IsInitialDocument()) {
+ ++mMeasuredTicksSinceLoading;
+ }
+
+ if (mMeasuredTicksSinceLoading <
+ StaticPrefs::dom_input_events_security_minNumTicks()) {
+ // Here we are forcing refresh driver to run because we can't always
+ // guarantee refresh driver will run enough times to meet the minNumTicks
+ // requirement. i.e. about:blank.
+ if (!RefreshDriver()->HasPendingTick()) {
+ RefreshDriver()->InitializeTimer();
+ }
+ }
+}
+
+bool nsPresContext::NeedsMoreTicksForUserInput() const {
+ MOZ_ASSERT(IsRoot());
+ return mMeasuredTicksSinceLoading <
+ StaticPrefs::dom_input_events_security_minNumTicks();
+}
+
+// Helper function for setting Anim Mode on image
+static void SetImgAnimModeOnImgReq(imgIRequest* aImgReq, uint16_t aMode) {
+ if (aImgReq) {
+ nsCOMPtr<imgIContainer> imgCon;
+ aImgReq->GetImage(getter_AddRefs(imgCon));
+ if (imgCon) {
+ imgCon->SetAnimationMode(aMode);
+ }
+ }
+}
+
+// IMPORTANT: Assumption is that all images for a Presentation
+// have the same Animation Mode (pavlov said this was OK)
+//
+// Walks content and set the animation mode
+// this is a way to turn on/off image animations
+void nsPresContext::SetImgAnimations(nsIContent* aParent, uint16_t aMode) {
+ nsCOMPtr<nsIImageLoadingContent> imgContent(do_QueryInterface(aParent));
+ if (imgContent) {
+ nsCOMPtr<imgIRequest> imgReq;
+ imgContent->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
+ getter_AddRefs(imgReq));
+ SetImgAnimModeOnImgReq(imgReq, aMode);
+ }
+
+ for (nsIContent* childContent = aParent->GetFirstChild(); childContent;
+ childContent = childContent->GetNextSibling()) {
+ SetImgAnimations(childContent, aMode);
+ }
+}
+
+void nsPresContext::SetSMILAnimations(dom::Document* aDoc, uint16_t aNewMode,
+ uint16_t aOldMode) {
+ if (aDoc->HasAnimationController()) {
+ SMILAnimationController* controller = aDoc->GetAnimationController();
+ switch (aNewMode) {
+ case imgIContainer::kNormalAnimMode:
+ case imgIContainer::kLoopOnceAnimMode:
+ if (aOldMode == imgIContainer::kDontAnimMode)
+ controller->Resume(SMILTimeContainer::PAUSE_USERPREF);
+ break;
+
+ case imgIContainer::kDontAnimMode:
+ if (aOldMode != imgIContainer::kDontAnimMode)
+ controller->Pause(SMILTimeContainer::PAUSE_USERPREF);
+ break;
+ }
+ }
+}
+
+void nsPresContext::SetImageAnimationMode(uint16_t aMode) {
+ NS_ASSERTION(aMode == imgIContainer::kNormalAnimMode ||
+ aMode == imgIContainer::kDontAnimMode ||
+ aMode == imgIContainer::kLoopOnceAnimMode,
+ "Wrong Animation Mode is being set!");
+
+ // Image animation mode cannot be changed when rendering to a printer.
+ if (!IsDynamic()) return;
+
+ // Now walk the content tree and set the animation mode
+ // on all the images.
+ if (mPresShell) {
+ dom::Document* doc = mPresShell->GetDocument();
+ if (doc) {
+ doc->StyleImageLoader()->SetAnimationMode(aMode);
+
+ Element* rootElement = doc->GetRootElement();
+ if (rootElement) {
+ SetImgAnimations(rootElement, aMode);
+ }
+ SetSMILAnimations(doc, aMode, mImageAnimationMode);
+ }
+ }
+
+ mImageAnimationMode = aMode;
+}
+
+void nsPresContext::SetTextZoom(float aTextZoom) {
+ float newZoom = aTextZoom;
+ float minZoom = StaticPrefs::zoom_minPercent() / 100.0f;
+ float maxZoom = StaticPrefs::zoom_maxPercent() / 100.0f;
+
+ if (newZoom < minZoom) {
+ newZoom = minZoom;
+ } else if (newZoom > maxZoom) {
+ newZoom = maxZoom;
+ }
+
+ if (newZoom == mTextZoom) {
+ return;
+ }
+
+ mTextZoom = newZoom;
+
+ // Media queries could have changed, since we changed the meaning
+ // of 'em' units in them.
+ MediaFeatureValuesChanged(
+ {RestyleHint::RecascadeSubtree(), NS_STYLE_HINT_REFLOW,
+ MediaFeatureChangeReason::ZoomChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+}
+
+void nsPresContext::SetInRDMPane(bool aInRDMPane) {
+ if (mInRDMPane == aInRDMPane) {
+ return;
+ }
+ mInRDMPane = aInRDMPane;
+ RecomputeTheme();
+}
+
+float nsPresContext::GetDeviceFullZoom() {
+ return mDeviceContext->GetFullZoom();
+}
+
+void nsPresContext::SetFullZoom(float aZoom) {
+ if (!mPresShell || mFullZoom == aZoom) {
+ return;
+ }
+
+ // Re-fetch the view manager's window dimensions in case there's a deferred
+ // resize which hasn't affected our mVisibleArea yet
+ nscoord oldWidthAppUnits, oldHeightAppUnits;
+ mPresShell->GetViewManager()->GetWindowDimensions(&oldWidthAppUnits,
+ &oldHeightAppUnits);
+ float oldWidthDevPixels = oldWidthAppUnits / float(mCurAppUnitsPerDevPixel);
+ float oldHeightDevPixels = oldHeightAppUnits / float(mCurAppUnitsPerDevPixel);
+ mDeviceContext->SetFullZoom(aZoom);
+
+ mFullZoom = aZoom;
+
+ AppUnitsPerDevPixelChanged();
+
+ mPresShell->GetViewManager()->SetWindowDimensions(
+ NSToCoordRound(oldWidthDevPixels * AppUnitsPerDevPixel()),
+ NSToCoordRound(oldHeightDevPixels * AppUnitsPerDevPixel()));
+}
+
+void nsPresContext::SetOverrideDPPX(float aDPPX) {
+ // SetOverrideDPPX is called during navigations, including history
+ // traversals. In that case, it's typically called with our current value,
+ // and we don't need to actually do anything.
+ if (aDPPX == GetOverrideDPPX()) {
+ return;
+ }
+
+ mMediaEmulationData.mDPPX = aDPPX;
+ MediaFeatureValuesChanged({MediaFeatureChangeReason::ResolutionChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+}
+
+gfxSize nsPresContext::ScreenSizeInchesForFontInflation(bool* aChanged) {
+ if (aChanged) {
+ *aChanged = false;
+ }
+
+ nsDeviceContext* dx = DeviceContext();
+ nsRect clientRect;
+ dx->GetClientRect(clientRect); // FIXME: GetClientRect looks expensive
+ float unitsPerInch = dx->AppUnitsPerPhysicalInch();
+ gfxSize deviceSizeInches(float(clientRect.width) / unitsPerInch,
+ float(clientRect.height) / unitsPerInch);
+
+ if (mLastFontInflationScreenSize == gfxSize(-1.0, -1.0)) {
+ mLastFontInflationScreenSize = deviceSizeInches;
+ }
+
+ if (deviceSizeInches != mLastFontInflationScreenSize && aChanged) {
+ *aChanged = true;
+ mLastFontInflationScreenSize = deviceSizeInches;
+ }
+
+ return deviceSizeInches;
+}
+
+static bool CheckOverflow(const ComputedStyle* aComputedStyle,
+ ScrollStyles* aStyles) {
+ // If they're not styled yet, we'll get around to it when constructing frames
+ // for the element.
+ if (!aComputedStyle) {
+ return false;
+ }
+ const nsStyleDisplay* display = aComputedStyle->StyleDisplay();
+
+ // If they will generate no box, just don't.
+ if (display->mDisplay == StyleDisplay::None ||
+ display->mDisplay == StyleDisplay::Contents) {
+ return false;
+ }
+
+ // NOTE(emilio): This check needs to match the one in
+ // Document::IsPotentiallyScrollable.
+ if (display->OverflowIsVisibleInBothAxis()) {
+ return false;
+ }
+
+ *aStyles =
+ ScrollStyles(*display, ScrollStyles::MapOverflowToValidScrollStyle);
+ return true;
+}
+
+// https://drafts.csswg.org/css-overflow/#overflow-propagation
+//
+// NOTE(emilio): We may need to use out-of-date styles for this, since this is
+// called from nsCSSFrameConstructor::ContentRemoved. We could refactor this a
+// bit to avoid doing that, and also fix correctness issues (we don't invalidate
+// properly when we insert a body element and there is a previous one, for
+// example).
+static Element* GetPropagatedScrollStylesForViewport(
+ nsPresContext* aPresContext, ScrollStyles* aStyles) {
+ Document* document = aPresContext->Document();
+ Element* docElement = document->GetRootElement();
+ // docElement might be null if we're doing this after removing it.
+ if (!docElement) {
+ return nullptr;
+ }
+
+ // Check the style on the document root element
+ const auto* rootStyle = Servo_Element_GetMaybeOutOfDateStyle(docElement);
+ if (CheckOverflow(rootStyle, aStyles)) {
+ // tell caller we stole the overflow style from the root element
+ return docElement;
+ }
+
+ if (rootStyle && rootStyle->StyleDisplay()->IsContainAny()) {
+ return nullptr;
+ }
+
+ // Don't look in the BODY for non-HTML documents or HTML documents
+ // with non-HTML roots.
+ // XXX this should be earlier; we shouldn't even look at the document root
+ // for non-HTML documents. Fix this once we support explicit CSS styling
+ // of the viewport
+ if (!document->IsHTMLOrXHTML() || !docElement->IsHTMLElement()) {
+ return nullptr;
+ }
+
+ Element* bodyElement = document->AsHTMLDocument()->GetBodyElement();
+ if (!bodyElement) {
+ return nullptr;
+ }
+
+ MOZ_ASSERT(bodyElement->IsHTMLElement(nsGkAtoms::body),
+ "GetBodyElement returned something bogus");
+
+ const auto* bodyStyle = Servo_Element_GetMaybeOutOfDateStyle(bodyElement);
+ if (bodyStyle && bodyStyle->StyleDisplay()->IsContainAny()) {
+ return nullptr;
+ }
+
+ if (CheckOverflow(bodyStyle, aStyles)) {
+ // tell caller we stole the overflow style from the body element
+ return bodyElement;
+ }
+
+ return nullptr;
+}
+
+Element* nsPresContext::UpdateViewportScrollStylesOverride() {
+ ScrollStyles oldViewportScrollStyles = mViewportScrollStyles;
+
+ // Start off with our default styles, and then update them as needed.
+ mViewportScrollStyles =
+ ScrollStyles(StyleOverflow::Auto, StyleOverflow::Auto);
+ mViewportScrollOverrideElement = nullptr;
+ // Don't propagate the scrollbar state in printing or print preview.
+ if (!IsPaginated()) {
+ mViewportScrollOverrideElement =
+ GetPropagatedScrollStylesForViewport(this, &mViewportScrollStyles);
+ }
+
+ dom::Document* document = Document();
+ if (Element* fsElement = document->GetUnretargetedFullscreenElement()) {
+ // If the document is in fullscreen, but the fullscreen element is
+ // not the root element, we should explicitly suppress the scrollbar
+ // here. Note that, we still need to return the original element
+ // the styles are from, so that the state of those elements is not
+ // affected across fullscreen change.
+ if (fsElement != document->GetRootElement() &&
+ fsElement != mViewportScrollOverrideElement) {
+ mViewportScrollStyles =
+ ScrollStyles(StyleOverflow::Hidden, StyleOverflow::Hidden);
+ }
+ }
+
+ if (mViewportScrollStyles != oldViewportScrollStyles) {
+ if (mPresShell) {
+ if (nsIFrame* frame = mPresShell->GetRootFrame()) {
+ frame->SchedulePaint();
+ }
+ }
+ }
+
+ return mViewportScrollOverrideElement;
+}
+
+bool nsPresContext::ElementWouldPropagateScrollStyles(const Element& aElement) {
+ if (aElement.GetParent() && !aElement.IsHTMLElement(nsGkAtoms::body)) {
+ // We certainly won't be propagating from this element.
+ return false;
+ }
+
+ // Go ahead and just call GetPropagatedScrollStylesForViewport, but update
+ // a dummy ScrollStyles we don't care about. It'll do a bit of extra work,
+ // but saves us having to have more complicated code or more code duplication;
+ // in practice we will make this call quite rarely, because we checked for all
+ // the common cases above.
+ ScrollStyles dummy(StyleOverflow::Auto, StyleOverflow::Auto);
+ return GetPropagatedScrollStylesForViewport(this, &dummy) == &aElement;
+}
+
+nsISupports* nsPresContext::GetContainerWeak() const {
+ return mDocument->GetDocShell();
+}
+
+ColorScheme nsPresContext::DefaultBackgroundColorScheme() const {
+ dom::Document* doc = Document();
+ // Use a dark background for top-level about:blank that is inaccessible to
+ // content JS.
+ if (doc->IsLikelyContentInaccessibleTopLevelAboutBlank()) {
+ return doc->PreferredColorScheme(Document::IgnoreRFP::Yes);
+ }
+ // Prefer the root color-scheme (since generally the default canvas
+ // background comes from the root element's background-color), and fall back
+ // to the default color-scheme if not available.
+ if (auto* frame = FrameConstructor()->GetRootElementStyleFrame()) {
+ return LookAndFeel::ColorSchemeForFrame(frame);
+ }
+ return doc->DefaultColorScheme();
+}
+
+nscolor nsPresContext::DefaultBackgroundColor() const {
+ if (!GetBackgroundColorDraw()) {
+ return NS_RGB(255, 255, 255);
+ }
+ return PrefSheetPrefs()
+ .ColorsFor(DefaultBackgroundColorScheme())
+ .mDefaultBackground;
+}
+
+nsDocShell* nsPresContext::GetDocShell() const {
+ return nsDocShell::Cast(mDocument->GetDocShell());
+}
+
+bool nsPresContext::BidiEnabled() const { return Document()->GetBidiEnabled(); }
+
+void nsPresContext::SetBidiEnabled() const { Document()->SetBidiEnabled(); }
+
+void nsPresContext::SetBidi(uint32_t aSource) {
+ // Don't do all this stuff unless the options have changed.
+ if (aSource == GetBidi()) {
+ return;
+ }
+
+ Document()->SetBidiOptions(aSource);
+ if (IBMBIDI_TEXTDIRECTION_RTL == GET_BIDI_OPTION_DIRECTION(aSource) ||
+ IBMBIDI_NUMERAL_HINDI == GET_BIDI_OPTION_NUMERAL(aSource)) {
+ SetBidiEnabled();
+ }
+ if (IBMBIDI_TEXTTYPE_VISUAL == GET_BIDI_OPTION_TEXTTYPE(aSource)) {
+ SetVisualMode(true);
+ } else if (IBMBIDI_TEXTTYPE_LOGICAL == GET_BIDI_OPTION_TEXTTYPE(aSource)) {
+ SetVisualMode(false);
+ } else {
+ SetVisualMode(IsVisualCharset(Document()->GetDocumentCharacterSet()));
+ }
+}
+
+uint32_t nsPresContext::GetBidi() const { return Document()->GetBidiOptions(); }
+
+void nsPresContext::RecordInteractionTime(InteractionType aType,
+ const TimeStamp& aTimeStamp) {
+ if (!mInteractionTimeEnabled || aTimeStamp.IsNull()) {
+ return;
+ }
+
+ // Array of references to the member variable of each time stamp
+ // for the different interaction types, keyed by InteractionType.
+ TimeStamp nsPresContext::*interactionTimes[] = {
+ &nsPresContext::mFirstClickTime, &nsPresContext::mFirstKeyTime,
+ &nsPresContext::mFirstMouseMoveTime, &nsPresContext::mFirstScrollTime};
+
+ // Array of histogram IDs for the different interaction types,
+ // keyed by InteractionType.
+ Telemetry::HistogramID histogramIds[] = {
+ Telemetry::TIME_TO_FIRST_CLICK_MS, Telemetry::TIME_TO_FIRST_KEY_INPUT_MS,
+ Telemetry::TIME_TO_FIRST_MOUSE_MOVE_MS,
+ Telemetry::TIME_TO_FIRST_SCROLL_MS};
+
+ TimeStamp& interactionTime =
+ this->*(interactionTimes[static_cast<uint32_t>(aType)]);
+ if (!interactionTime.IsNull()) {
+ // We have already recorded an interaction time.
+ return;
+ }
+
+ // Record the interaction time if it occurs after the first paint
+ // of the top level content document.
+ nsPresContext* inProcessRootPresContext =
+ GetInProcessRootContentDocumentPresContext();
+
+ if (!inProcessRootPresContext ||
+ !inProcessRootPresContext->IsRootContentDocumentCrossProcess()) {
+ // There is no top content pres context, or we are in a cross process
+ // document so we don't care about the interaction time. Record a value
+ // anyways to avoid trying to find the top content pres context in future
+ // interactions.
+ interactionTime = TimeStamp::Now();
+ return;
+ }
+
+ if (inProcessRootPresContext->mFirstNonBlankPaintTime.IsNull() ||
+ inProcessRootPresContext->mFirstNonBlankPaintTime > aTimeStamp) {
+ // Top content pres context has not had a non-blank paint yet
+ // or the event timestamp is before the first non-blank paint,
+ // so don't record interaction time.
+ return;
+ }
+
+ // Check if we are recording the first of any of the interaction types.
+ bool isFirstInteraction = true;
+ for (TimeStamp nsPresContext::*memberPtr : interactionTimes) {
+ TimeStamp& timeStamp = this->*(memberPtr);
+ if (!timeStamp.IsNull()) {
+ isFirstInteraction = false;
+ break;
+ }
+ }
+
+ interactionTime = TimeStamp::Now();
+ // Only the top level content pres context reports first interaction
+ // time to telemetry (if it hasn't already done so).
+ if (this == inProcessRootPresContext) {
+ if (Telemetry::CanRecordExtended()) {
+ double millis =
+ (interactionTime - mFirstNonBlankPaintTime).ToMilliseconds();
+ Telemetry::Accumulate(histogramIds[static_cast<uint32_t>(aType)], millis);
+
+ if (isFirstInteraction) {
+ Telemetry::Accumulate(Telemetry::TIME_TO_FIRST_INTERACTION_MS, millis);
+ }
+ }
+ } else {
+ inProcessRootPresContext->RecordInteractionTime(aType, aTimeStamp);
+ }
+}
+
+nsITheme* nsPresContext::Theme() const {
+ MOZ_ASSERT(mTheme);
+ return mTheme;
+}
+
+void nsPresContext::EnsureTheme() {
+ MOZ_ASSERT(!mTheme);
+ if (Document()->ShouldAvoidNativeTheme()) {
+ if (mInRDMPane) {
+ mTheme = do_GetRDMThemeDoNotUseDirectly();
+ } else {
+ mTheme = do_GetBasicNativeThemeDoNotUseDirectly();
+ }
+ } else {
+ mTheme = do_GetNativeThemeDoNotUseDirectly();
+ }
+ MOZ_RELEASE_ASSERT(mTheme);
+}
+
+void nsPresContext::RecomputeTheme() {
+ if (!mTheme) {
+ return;
+ }
+ nsCOMPtr<nsITheme> oldTheme = std::move(mTheme);
+ EnsureTheme();
+ if (oldTheme == mTheme) {
+ return;
+ }
+ // Theme affects layout information (as it affects whether we create
+ // scrollbar buttons for example) and also style (affects the
+ // scrollbar-inline-size env var).
+ RebuildAllStyleData(nsChangeHint_ReconstructFrame,
+ RestyleHint::RecascadeSubtree());
+ // This is a bit of a lie, but this affects the overlay-scrollbars
+ // media query and it's the code-path that gets taken for regular system
+ // metrics changes via ThemeChanged().
+ MediaFeatureValuesChanged({MediaFeatureChangeReason::SystemMetricsChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+}
+
+bool nsPresContext::UseOverlayScrollbars() const {
+ return LookAndFeel::GetInt(LookAndFeel::IntID::UseOverlayScrollbars) ||
+ mInRDMPane;
+}
+
+void nsPresContext::ThemeChanged(widget::ThemeChangeKind aKind) {
+ PROFILER_MARKER_UNTYPED("ThemeChanged", LAYOUT, MarkerStack::Capture());
+
+ mPendingThemeChangeKind |= unsigned(aKind);
+
+ if (!mPendingThemeChanged) {
+ nsCOMPtr<nsIRunnable> ev =
+ new WeakRunnableMethod("nsPresContext::ThemeChangedInternal", this,
+ &nsPresContext::ThemeChangedInternal);
+ RefreshDriver()->AddEarlyRunner(ev);
+ mPendingThemeChanged = true;
+ }
+ MOZ_ASSERT(LookAndFeel::HasPendingGlobalThemeChange());
+}
+
+void nsPresContext::ThemeChangedInternal() {
+ MOZ_ASSERT(mPendingThemeChanged);
+
+ mPendingThemeChanged = false;
+
+ const auto kind = widget::ThemeChangeKind(mPendingThemeChangeKind);
+ mPendingThemeChangeKind = 0;
+
+ LookAndFeel::HandleGlobalThemeChange();
+
+ // Full zoom might have changed as a result of the text scale factor.
+ RecomputeBrowsingContextDependentData();
+
+ // Changes to system metrics and other look and feel values can change media
+ // queries on them.
+ //
+ // Changes in theme can change system colors (whose changes are properly
+ // reflected in computed style data), system fonts (whose changes are
+ // some reflected (like sizes and such) and some not), and -moz-appearance
+ // (whose changes are not), so we need to recascade for the first, and reflow
+ // for the rest.
+ auto restyleHint = (kind & widget::ThemeChangeKind::Style)
+ ? RestyleHint::RecascadeSubtree()
+ : RestyleHint{0};
+ auto changeHint = (kind & widget::ThemeChangeKind::Layout)
+ ? NS_STYLE_HINT_REFLOW
+ : nsChangeHint(0);
+ MediaFeatureValuesChanged(
+ {restyleHint, changeHint, MediaFeatureChangeReason::SystemMetricsChange},
+ MediaFeatureChangePropagation::All);
+
+ if (Document()->IsInChromeDocShell()) {
+ if (RefPtr<nsPIDOMWindowInner> win = Document()->GetInnerWindow()) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ Document(), nsGlobalWindowInner::Cast(win), u"nativethemechange"_ns,
+ CanBubble::eYes, Cancelable::eYes, nullptr);
+ }
+ }
+}
+
+void nsPresContext::UIResolutionChanged() {
+ if (!mPendingUIResolutionChanged) {
+ nsCOMPtr<nsIRunnable> ev =
+ NewRunnableMethod("nsPresContext::UIResolutionChangedInternal", this,
+ &nsPresContext::UIResolutionChangedInternal);
+ nsresult rv = Document()->Dispatch(ev.forget());
+ if (NS_SUCCEEDED(rv)) {
+ mPendingUIResolutionChanged = true;
+ }
+ }
+}
+
+void nsPresContext::UIResolutionChangedSync() {
+ if (!mPendingUIResolutionChanged) {
+ mPendingUIResolutionChanged = true;
+ UIResolutionChangedInternal();
+ }
+}
+
+static void NotifyTabUIResolutionChanged(nsIRemoteTab* aTab, void* aArg) {
+ aTab->NotifyResolutionChanged();
+}
+
+static void NotifyChildrenUIResolutionChanged(nsPIDOMWindowOuter* aWindow) {
+ nsCOMPtr<Document> doc = aWindow->GetExtantDoc();
+ RefPtr<nsPIWindowRoot> topLevelWin = nsContentUtils::GetWindowRoot(doc);
+ if (!topLevelWin) {
+ return;
+ }
+ topLevelWin->EnumerateBrowsers(NotifyTabUIResolutionChanged, nullptr);
+}
+
+void nsPresContext::UIResolutionChangedInternal() {
+ mPendingUIResolutionChanged = false;
+
+ mDeviceContext->CheckDPIChange();
+ if (mCurAppUnitsPerDevPixel != mDeviceContext->AppUnitsPerDevPixel()) {
+ AppUnitsPerDevPixelChanged();
+ }
+
+ if (mPresShell) {
+ mPresShell->RefreshZoomConstraintsForScreenSizeChange();
+ if (RefPtr<MobileViewportManager> mvm =
+ mPresShell->GetMobileViewportManager()) {
+ mvm->UpdateSizesBeforeReflow();
+ }
+ }
+
+ // Recursively notify all remote leaf descendants of the change.
+ if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
+ NotifyChildrenUIResolutionChanged(window);
+ }
+
+ mDocument->EnumerateSubDocuments([](dom::Document& aSubDoc) {
+ if (nsPresContext* pc = aSubDoc.GetPresContext()) {
+ pc->UIResolutionChangedInternal();
+ }
+ return CallState::Continue;
+ });
+}
+
+void nsPresContext::EmulateMedium(nsAtom* aMediaType) {
+ MOZ_ASSERT(!aMediaType || aMediaType->IsAsciiLowercase());
+
+ RefPtr<const nsAtom> oldMedium = Medium();
+ auto oldScheme = mDocument->PreferredColorScheme();
+
+ mMediaEmulationData.mMedium = aMediaType;
+
+ if (Medium() == oldMedium) {
+ return;
+ }
+
+ MediaFeatureChange change(MediaFeatureChangeReason::MediumChange);
+ if (oldScheme != mDocument->PreferredColorScheme()) {
+ change |= MediaFeatureChange::ForPreferredColorSchemeChange();
+ }
+ MediaFeatureValuesChanged(change,
+ MediaFeatureChangePropagation::JustThisDocument);
+}
+
+void nsPresContext::ContentLanguageChanged() {
+ PostRebuildAllStyleDataEvent(nsChangeHint(0),
+ RestyleHint::RecascadeSubtree());
+}
+
+void nsPresContext::RegisterManagedPostRefreshObserver(
+ ManagedPostRefreshObserver* aObserver) {
+ if (MOZ_UNLIKELY(!mPresShell)) {
+ // If we're detached from our pres shell already, refuse to keep observer
+ // around, as that'd create a cycle.
+ RefPtr<ManagedPostRefreshObserver> obs = aObserver;
+ obs->Cancel();
+ return;
+ }
+
+ RefreshDriver()->AddPostRefreshObserver(
+ static_cast<nsAPostRefreshObserver*>(aObserver));
+ mManagedPostRefreshObservers.AppendElement(aObserver);
+}
+
+void nsPresContext::UnregisterManagedPostRefreshObserver(
+ ManagedPostRefreshObserver* aObserver) {
+ RefreshDriver()->RemovePostRefreshObserver(
+ static_cast<nsAPostRefreshObserver*>(aObserver));
+ DebugOnly<bool> removed =
+ mManagedPostRefreshObservers.RemoveElement(aObserver);
+ MOZ_ASSERT(removed,
+ "ManagedPostRefreshObserver should be owned by PresContext");
+}
+
+void nsPresContext::CancelManagedPostRefreshObservers() {
+ auto observers = std::move(mManagedPostRefreshObservers);
+ nsRefreshDriver* driver = RefreshDriver();
+ for (const auto& observer : observers) {
+ observer->Cancel();
+ driver->RemovePostRefreshObserver(
+ static_cast<nsAPostRefreshObserver*>(observer));
+ }
+}
+
+void nsPresContext::RebuildAllStyleData(nsChangeHint aExtraHint,
+ const RestyleHint& aRestyleHint) {
+ if (!mPresShell) {
+ // We must have been torn down. Nothing to do here.
+ return;
+ }
+
+ // TODO(emilio): It's unclear to me why would these three calls below be
+ // needed. In particular, RebuildAllStyleData doesn't rebuild rules or
+ // specified style information and such (note the comment in
+ // RestyleManager::RebuildAllStyleData re. the funny semantics), so I
+ // don't know why should we rebuild the user font set / counter styles /
+ // etc...
+ mDocument->MarkUserFontSetDirty();
+ MarkCounterStylesDirty();
+ MarkFontFeatureValuesDirty();
+ MarkFontPaletteValuesDirty();
+ PostRebuildAllStyleDataEvent(aExtraHint, aRestyleHint);
+}
+
+void nsPresContext::PostRebuildAllStyleDataEvent(
+ nsChangeHint aExtraHint, const RestyleHint& aRestyleHint) {
+ if (!mPresShell) {
+ // We must have been torn down. Nothing to do here.
+ return;
+ }
+ RestyleManager()->RebuildAllStyleData(aExtraHint, aRestyleHint);
+}
+
+void nsPresContext::MediaFeatureValuesChanged(
+ const MediaFeatureChange& aChange,
+ MediaFeatureChangePropagation aPropagation) {
+ if (mPresShell) {
+ mPresShell->EnsureStyleFlush();
+ }
+
+ if (!mDocument->MediaQueryLists().isEmpty()) {
+ RefreshDriver()->ScheduleMediaQueryListenerUpdate();
+ }
+
+ if (!mPendingMediaFeatureValuesChange) {
+ mPendingMediaFeatureValuesChange = MakeUnique<MediaFeatureChange>(aChange);
+ } else {
+ *mPendingMediaFeatureValuesChange |= aChange;
+ }
+
+ if (aPropagation & MediaFeatureChangePropagation::Images) {
+ // Propagate the media feature value change down to any SVG images the
+ // document is using.
+ mDocument->ImageTracker()->MediaFeatureValuesChangedAllDocuments(aChange);
+ }
+
+ if (aPropagation & MediaFeatureChangePropagation::SubDocuments) {
+ // And then into any subdocuments.
+ mDocument->EnumerateSubDocuments(
+ [&aChange, aPropagation](dom::Document& aSubDoc) {
+ if (nsPresContext* pc = aSubDoc.GetPresContext()) {
+ pc->MediaFeatureValuesChanged(aChange, aPropagation);
+ }
+ return CallState::Continue;
+ });
+ }
+
+ // We notify the media feature values changed for the responsive content of
+ // HTMLImageElements synchronously, so their image sources are always
+ // up-to-date when running the image load tasks in the microtasks.
+ mDocument->NotifyMediaFeatureValuesChanged();
+}
+
+bool nsPresContext::FlushPendingMediaFeatureValuesChanged() {
+ if (!mPendingMediaFeatureValuesChange) {
+ return false;
+ }
+
+ MediaFeatureChange change = *mPendingMediaFeatureValuesChange;
+ mPendingMediaFeatureValuesChange.reset();
+
+ // MediumFeaturesChanged updates the applied rules, so it always gets called.
+ if (mPresShell) {
+ change.mRestyleHint |=
+ mPresShell->StyleSet()->MediumFeaturesChanged(change.mReason);
+ }
+
+ const bool changedStyle = change.mRestyleHint || change.mChangeHint;
+ if (changedStyle) {
+ RebuildAllStyleData(change.mChangeHint, change.mRestyleHint);
+ }
+
+ for (MediaQueryList* mql : mDocument->MediaQueryLists()) {
+ mql->MediaFeatureValuesChanged();
+ }
+
+ return changedStyle;
+}
+
+void nsPresContext::SizeModeChanged(nsSizeMode aSizeMode) {
+ if (nsPIDOMWindowOuter* window = mDocument->GetWindow()) {
+ nsContentUtils::CallOnAllRemoteChildren(
+ window, [&aSizeMode](BrowserParent* aBrowserParent) -> CallState {
+ aBrowserParent->SizeModeChanged(aSizeMode);
+ return CallState::Continue;
+ });
+ }
+ MediaFeatureValuesChanged({MediaFeatureChangeReason::SizeModeChange},
+ MediaFeatureChangePropagation::SubDocuments);
+}
+
+nsCompatibility nsPresContext::CompatibilityMode() const {
+ return Document()->GetCompatibilityMode();
+}
+
+void nsPresContext::SetPaginatedScrolling(bool aPaginated) {
+ if (mType == eContext_PrintPreview || mType == eContext_PageLayout) {
+ mCanPaginatedScroll = aPaginated;
+ }
+}
+
+void nsPresContext::SetPrintSettings(nsIPrintSettings* aPrintSettings) {
+ if (mMedium != nsGkAtoms::print) {
+ return;
+ }
+
+ mPrintSettings = aPrintSettings;
+ mDefaultPageMargin = nsMargin();
+ if (!mPrintSettings) {
+ return;
+ }
+
+ // Set the presentation context to the value in the print settings.
+ mDrawColorBackground = mPrintSettings->GetPrintBGColors();
+ mDrawImageBackground = mPrintSettings->GetPrintBGImages();
+
+ nsIntMargin marginTwips = mPrintSettings->GetMarginInTwips();
+ if (!mPrintSettings->GetIgnoreUnwriteableMargins()) {
+ nsIntMargin unwriteableTwips =
+ mPrintSettings->GetUnwriteableMarginInTwips();
+ NS_ASSERTION(unwriteableTwips.top >= 0 && unwriteableTwips.right >= 0 &&
+ unwriteableTwips.bottom >= 0 && unwriteableTwips.left >= 0,
+ "Unwriteable twips should be non-negative");
+ marginTwips.EnsureAtLeast(unwriteableTwips);
+ }
+ mDefaultPageMargin = nsPresContext::CSSTwipsToAppUnits(marginTwips);
+}
+
+bool nsPresContext::EnsureVisible() {
+ BrowsingContext* browsingContext =
+ mDocument ? mDocument->GetBrowsingContext() : nullptr;
+ if (!browsingContext || browsingContext->IsInBFCache()) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDocShell> docShell(GetDocShell());
+ if (!docShell) {
+ return false;
+ }
+ nsCOMPtr<nsIDocumentViewer> viewer;
+ docShell->GetDocViewer(getter_AddRefs(viewer));
+ // Make sure this is the content viewer we belong with
+ if (!viewer || viewer->GetPresContext() != this) {
+ return false;
+ }
+ // OK, this is us. We want to call Show() on the content viewer.
+ nsresult result = viewer->Show();
+ return NS_SUCCEEDED(result);
+}
+
+#ifdef MOZ_REFLOW_PERF
+void nsPresContext::CountReflows(const char* aName, nsIFrame* aFrame) {
+ if (mPresShell) {
+ mPresShell->CountReflows(aName, aFrame);
+ }
+}
+#endif
+
+gfxUserFontSet* nsPresContext::GetUserFontSet() {
+ return mDocument->GetUserFontSet();
+}
+
+void nsPresContext::UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont) {
+ if (!mPresShell) {
+ return;
+ }
+
+ // Note: this method is called without a font when rules in the userfont set
+ // are updated.
+ //
+ // We can avoid a full restyle if font-metric-dependent units are not in use,
+ // since we know there's no style resolution that would depend on this font
+ // and trigger its load.
+ //
+ // TODO(emilio): We could be more granular if we knew which families have
+ // potentially changed.
+ if (!aUpdatedFont) {
+ auto hint = StyleSet()->UsesFontMetrics() ? RestyleHint::RecascadeSubtree()
+ : RestyleHint{0};
+ PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, hint);
+ return;
+ }
+
+ // Iterate over the frame tree looking for frames associated with the
+ // downloadable font family in question. If a frame's nsStyleFont has
+ // the name, check the font group associated with the metrics to see if
+ // it contains that specific font (i.e. the one chosen within the family
+ // given the weight, width, and slant from the nsStyleFont). If it does,
+ // mark that frame dirty and skip inspecting its descendants.
+ if (nsIFrame* root = mPresShell->GetRootFrame()) {
+ nsFontFaceUtils::MarkDirtyForFontChange(root, aUpdatedFont);
+ }
+}
+
+class CounterStyleCleaner final : public nsAPostRefreshObserver {
+ public:
+ CounterStyleCleaner(nsRefreshDriver* aRefreshDriver,
+ CounterStyleManager* aCounterStyleManager)
+ : mRefreshDriver(aRefreshDriver),
+ mCounterStyleManager(aCounterStyleManager) {}
+ virtual ~CounterStyleCleaner() = default;
+
+ void DidRefresh() final {
+ mRefreshDriver->RemovePostRefreshObserver(this);
+ mCounterStyleManager->CleanRetiredStyles();
+ delete this;
+ }
+
+ private:
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ RefPtr<CounterStyleManager> mCounterStyleManager;
+};
+
+void nsPresContext::FlushCounterStyles() {
+ if (!mPresShell) {
+ return; // we've been torn down
+ }
+ if (mCounterStyleManager->IsInitial()) {
+ // Still in its initial state, no need to clean.
+ return;
+ }
+
+ if (mCounterStylesDirty) {
+ bool changed = mCounterStyleManager->NotifyRuleChanged();
+ if (changed) {
+ PresShell()->NotifyCounterStylesAreDirty();
+ PostRebuildAllStyleDataEvent(NS_STYLE_HINT_REFLOW, RestyleHint{0});
+ RefreshDriver()->AddPostRefreshObserver(
+ new CounterStyleCleaner(RefreshDriver(), mCounterStyleManager));
+ }
+ mCounterStylesDirty = false;
+ }
+}
+
+void nsPresContext::MarkCounterStylesDirty() {
+ if (mCounterStyleManager->IsInitial()) {
+ // Still in its initial state, no need to touch anything.
+ return;
+ }
+
+ mCounterStylesDirty = true;
+}
+
+void nsPresContext::NotifyMissingFonts() {
+ if (mMissingFonts) {
+ mMissingFonts->Flush();
+ }
+}
+
+void nsPresContext::EnsureSafeToHandOutCSSRules() {
+ if (!mPresShell->StyleSet()->EnsureUniqueInnerOnCSSSheets()) {
+ // Nothing to do.
+ return;
+ }
+
+ RebuildAllStyleData(nsChangeHint(0), RestyleHint::RestyleSubtree());
+}
+
+void nsPresContext::FireDOMPaintEvent(
+ nsTArray<nsRect>* aList, TransactionId aTransactionId,
+ mozilla::TimeStamp aTimeStamp /* = mozilla::TimeStamp() */) {
+ nsPIDOMWindowInner* ourWindow = mDocument->GetInnerWindow();
+ if (!ourWindow) return;
+
+ nsCOMPtr<EventTarget> dispatchTarget = do_QueryInterface(ourWindow);
+ nsCOMPtr<EventTarget> eventTarget = dispatchTarget;
+ if (!IsChrome() && !StaticPrefs::dom_send_after_paint_to_content()) {
+ // Don't tell the window about this event, it should not know that
+ // something happened in a subdocument. Tell only the chrome event handler.
+ // (Events sent to the window get propagated to the chrome event handler
+ // automatically.)
+ dispatchTarget = ourWindow->GetParentTarget();
+ if (!dispatchTarget) {
+ return;
+ }
+ }
+
+ if (aTimeStamp.IsNull()) {
+ aTimeStamp = mozilla::TimeStamp::Now();
+ }
+ DOMHighResTimeStamp timeStamp = 0;
+ if (ourWindow) {
+ mozilla::dom::Performance* perf = ourWindow->GetPerformance();
+ if (perf) {
+ timeStamp = perf->GetDOMTiming()->TimeStampToDOMHighRes(aTimeStamp);
+ }
+ }
+
+ // Events sent to the window get propagated to the chrome event handler
+ // automatically.
+ //
+ // This will empty our list in case dispatching the event causes more damage
+ // (hopefully it won't, or we're likely to get an infinite loop! At least
+ // it won't be blocking app execution though).
+ RefPtr<NotifyPaintEvent> event =
+ NS_NewDOMNotifyPaintEvent(eventTarget, this, nullptr, eAfterPaint, aList,
+ uint64_t(aTransactionId), timeStamp);
+
+ // Even if we're not telling the window about the event (so eventTarget is
+ // the chrome event handler, not the window), the window is still
+ // logically the event target.
+ event->SetTarget(eventTarget);
+ event->SetTrusted(true);
+ EventDispatcher::DispatchDOMEvent(dispatchTarget, nullptr,
+ static_cast<Event*>(event), this, nullptr);
+}
+
+static bool MayHavePaintEventListener(nsPIDOMWindowInner* aInnerWindow) {
+ if (!aInnerWindow) return false;
+ if (aInnerWindow->HasPaintEventListeners()) return true;
+
+ EventTarget* parentTarget = aInnerWindow->GetParentTarget();
+ if (!parentTarget) return false;
+
+ EventListenerManager* manager = nullptr;
+ if ((manager = parentTarget->GetExistingListenerManager()) &&
+ manager->MayHavePaintEventListener()) {
+ return true;
+ }
+
+ nsCOMPtr<nsINode> node;
+ if (parentTarget != aInnerWindow->GetChromeEventHandler()) {
+ nsCOMPtr<nsIInProcessContentFrameMessageManager> mm =
+ do_QueryInterface(parentTarget);
+ if (mm) {
+ node = mm->GetOwnerContent();
+ }
+ }
+
+ if (!node) {
+ node = nsINode::FromEventTarget(parentTarget);
+ }
+ if (node) {
+ return MayHavePaintEventListener(node->OwnerDoc()->GetInnerWindow());
+ }
+
+ if (nsCOMPtr<nsPIDOMWindowInner> window =
+ nsPIDOMWindowInner::FromEventTarget(parentTarget)) {
+ return MayHavePaintEventListener(window);
+ }
+
+ if (nsCOMPtr<nsPIWindowRoot> root =
+ nsPIWindowRoot::FromEventTarget(parentTarget)) {
+ EventTarget* browserChildGlobal;
+ return root && (browserChildGlobal = root->GetParentTarget()) &&
+ (manager = browserChildGlobal->GetExistingListenerManager()) &&
+ manager->MayHavePaintEventListener();
+ }
+
+ return false;
+}
+
+bool nsPresContext::MayHavePaintEventListener() {
+ return ::MayHavePaintEventListener(mDocument->GetInnerWindow());
+}
+
+void nsPresContext::NotifyInvalidation(TransactionId aTransactionId,
+ const nsIntRect& aRect) {
+ // Prevent values from overflow after DevPixelsToAppUnits().
+ //
+ // DevPixelsTopAppUnits() will multiple a factor (60) to the value,
+ // it may make the result value over the edge (overflow) of max or
+ // min value of int32_t. Compute the max sized dev pixel rect that
+ // we can support and intersect with it.
+ nsIntRect clampedRect = nsIntRect::MaxIntRect();
+ clampedRect.ScaleInverseRoundIn(AppUnitsPerDevPixel());
+
+ clampedRect = clampedRect.Intersect(aRect);
+
+ nsRect rect(DevPixelsToAppUnits(clampedRect.x),
+ DevPixelsToAppUnits(clampedRect.y),
+ DevPixelsToAppUnits(clampedRect.width),
+ DevPixelsToAppUnits(clampedRect.height));
+ NotifyInvalidation(aTransactionId, rect);
+}
+
+nsPresContext::TransactionInvalidations* nsPresContext::GetInvalidations(
+ TransactionId aTransactionId) {
+ for (TransactionInvalidations& t : mTransactions) {
+ if (t.mTransactionId == aTransactionId) {
+ return &t;
+ }
+ }
+ return nullptr;
+}
+
+void nsPresContext::NotifyInvalidation(TransactionId aTransactionId,
+ const nsRect& aRect) {
+ MOZ_ASSERT(GetContainerWeak(), "Invalidation in detached pres context");
+
+ // If there is no paint event listener, then we don't need to fire
+ // the asynchronous event. We don't even need to record invalidation.
+ // MayHavePaintEventListener is pretty cheap and we could make it
+ // even cheaper by providing a more efficient
+ // nsPIDOMWindow::GetListenerManager.
+
+ nsPresContext* pc;
+ for (pc = this; pc; pc = pc->GetParentPresContext()) {
+ TransactionInvalidations* transaction =
+ pc->GetInvalidations(aTransactionId);
+ if (transaction) {
+ break;
+ } else {
+ transaction = pc->mTransactions.AppendElement();
+ transaction->mTransactionId = aTransactionId;
+ }
+ }
+
+ TransactionInvalidations* transaction = GetInvalidations(aTransactionId);
+ MOZ_ASSERT(transaction);
+ transaction->mInvalidations.AppendElement(aRect);
+}
+
+class DelayedFireDOMPaintEvent : public Runnable {
+ public:
+ DelayedFireDOMPaintEvent(
+ nsPresContext* aPresContext, nsTArray<nsRect>&& aList,
+ TransactionId aTransactionId,
+ const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp())
+ : mozilla::Runnable("DelayedFireDOMPaintEvent"),
+ mPresContext(aPresContext),
+ mTransactionId(aTransactionId),
+ mTimeStamp(aTimeStamp),
+ mList(std::move(aList)) {
+ MOZ_ASSERT(mPresContext->GetContainerWeak(),
+ "DOMPaintEvent requested for a detached pres context");
+ }
+ NS_IMETHOD Run() override {
+ // The pres context might have been detached during the delay -
+ // that's fine, just don't fire the event.
+ if (mPresContext->GetContainerWeak()) {
+ mPresContext->FireDOMPaintEvent(&mList, mTransactionId, mTimeStamp);
+ }
+ return NS_OK;
+ }
+
+ RefPtr<nsPresContext> mPresContext;
+ TransactionId mTransactionId;
+ const mozilla::TimeStamp mTimeStamp;
+ nsTArray<nsRect> mList;
+};
+
+void nsPresContext::NotifyRevokingDidPaint(TransactionId aTransactionId) {
+ if ((IsRoot() || !PresShell()->IsVisible()) && mTransactions.IsEmpty()) {
+ return;
+ }
+
+ TransactionInvalidations* transaction = nullptr;
+ for (auto& t : mTransactions) {
+ if (t.mTransactionId == aTransactionId) {
+ transaction = &t;
+ break;
+ }
+ }
+ // If there are no transaction invalidations (which imply callers waiting
+ // on the event) for this revoked id, then we don't need to fire a
+ // MozAfterPaint.
+ if (!transaction) {
+ return;
+ }
+
+ // If there are queued transactions with an earlier id, we can't send
+ // our event now since it will arrive out of order. Set the waiting for
+ // previous transaction flag to true, and we'll send the event when
+ // the others are completed.
+ // If this is the only transaction, then we can send it immediately.
+ if (mTransactions.Length() == 1) {
+ nsCOMPtr<nsIRunnable> ev = new DelayedFireDOMPaintEvent(
+ this, std::move(transaction->mInvalidations),
+ transaction->mTransactionId, mozilla::TimeStamp());
+ nsContentUtils::AddScriptRunner(ev);
+ mTransactions.RemoveElementAt(0);
+ } else {
+ transaction->mIsWaitingForPreviousTransaction = true;
+ }
+
+ mDocument->EnumerateSubDocuments([&aTransactionId](dom::Document& aSubDoc) {
+ if (nsPresContext* pc = aSubDoc.GetPresContext()) {
+ pc->NotifyRevokingDidPaint(aTransactionId);
+ }
+ return CallState::Continue;
+ });
+}
+
+void nsPresContext::NotifyDidPaintForSubtree(
+ TransactionId aTransactionId, const mozilla::TimeStamp& aTimeStamp) {
+ if (mFirstContentfulPaintTransactionId && !mHadContentfulPaintComposite) {
+ if (aTransactionId >= *mFirstContentfulPaintTransactionId) {
+ mHadContentfulPaintComposite = true;
+ RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+ if (timing && !IsPrintingOrPrintPreview()) {
+ timing->NotifyContentfulCompositeForRootContentDocument(aTimeStamp);
+ }
+ }
+ }
+
+ if (IsRoot() && mTransactions.IsEmpty()) {
+ return;
+ }
+
+ if (!PresShell()->IsVisible() && mTransactions.IsEmpty()) {
+ return;
+ }
+
+ // Non-root prescontexts fire MozAfterPaint to all their descendants
+ // unconditionally, even if no invalidations have been collected. This is
+ // because we don't want to eat the cost of collecting invalidations for
+ // every subdocument (which would require putting every subdocument in its
+ // own layer).
+
+ bool sent = false;
+ uint32_t i = 0;
+ while (i < mTransactions.Length()) {
+ if (mTransactions[i].mTransactionId <= aTransactionId) {
+ if (!mTransactions[i].mInvalidations.IsEmpty()) {
+ nsCOMPtr<nsIRunnable> ev = new DelayedFireDOMPaintEvent(
+ this, std::move(mTransactions[i].mInvalidations),
+ mTransactions[i].mTransactionId, aTimeStamp);
+ NS_DispatchToCurrentThreadQueue(ev.forget(),
+ EventQueuePriority::MediumHigh);
+ sent = true;
+ }
+ mTransactions.RemoveElementAt(i);
+ } else {
+ // If there are transaction which is waiting for this transaction,
+ // we should fire a MozAfterPaint immediately.
+ if (sent && mTransactions[i].mIsWaitingForPreviousTransaction) {
+ nsCOMPtr<nsIRunnable> ev = new DelayedFireDOMPaintEvent(
+ this, std::move(mTransactions[i].mInvalidations),
+ mTransactions[i].mTransactionId, aTimeStamp);
+ NS_DispatchToCurrentThreadQueue(ev.forget(),
+ EventQueuePriority::MediumHigh);
+ sent = true;
+ mTransactions.RemoveElementAt(i);
+ continue;
+ }
+ i++;
+ }
+ }
+
+ if (!sent) {
+ nsTArray<nsRect> dummy;
+ nsCOMPtr<nsIRunnable> ev = new DelayedFireDOMPaintEvent(
+ this, std::move(dummy), aTransactionId, aTimeStamp);
+ NS_DispatchToCurrentThreadQueue(ev.forget(),
+ EventQueuePriority::MediumHigh);
+ }
+
+ mDocument->EnumerateSubDocuments(
+ [&aTransactionId, &aTimeStamp](dom::Document& aSubDoc) {
+ if (nsPresContext* pc = aSubDoc.GetPresContext()) {
+ pc->NotifyDidPaintForSubtree(aTransactionId, aTimeStamp);
+ }
+ return CallState::Continue;
+ });
+}
+
+already_AddRefed<nsITimer> nsPresContext::CreateTimer(
+ nsTimerCallbackFunc aCallback, const char* aName, uint32_t aDelay) {
+ nsCOMPtr<nsITimer> timer;
+ NS_NewTimerWithFuncCallback(getter_AddRefs(timer), aCallback, this, aDelay,
+ nsITimer::TYPE_ONE_SHOT, aName,
+ GetMainThreadSerialEventTarget());
+ return timer.forget();
+}
+
+static bool sGotInterruptEnv = false;
+enum InterruptMode { ModeRandom, ModeCounter, ModeEvent };
+// Controlled by the GECKO_REFLOW_INTERRUPT_MODE env var; allowed values are
+// "random" (except on Windows) or "counter". If neither is used, the mode is
+// ModeEvent.
+static InterruptMode sInterruptMode = ModeEvent;
+#ifndef XP_WIN
+// Used for the "random" mode. Controlled by the GECKO_REFLOW_INTERRUPT_SEED
+// env var.
+static uint32_t sInterruptSeed = 1;
+#endif
+// Used for the "counter" mode. This is the number of unskipped interrupt
+// checks that have to happen before we interrupt. Controlled by the
+// GECKO_REFLOW_INTERRUPT_FREQUENCY env var.
+static uint32_t sInterruptMaxCounter = 10;
+// Used for the "counter" mode. This counts up to sInterruptMaxCounter and is
+// then reset to 0.
+static uint32_t sInterruptCounter;
+// Number of interrupt checks to skip before really trying to interrupt.
+// Controlled by the GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP env var.
+static uint32_t sInterruptChecksToSkip = 200;
+// Number of milliseconds that a reflow should be allowed to run for before we
+// actually allow interruption. Controlled by the
+// GECKO_REFLOW_MIN_NOINTERRUPT_DURATION env var. Can't be initialized here,
+// because TimeDuration/TimeStamp is not safe to use in static constructors..
+static TimeDuration sInterruptTimeout;
+
+static void GetInterruptEnv() {
+ char* ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_MODE");
+ if (ev) {
+#ifndef XP_WIN
+ if (nsCRT::strcasecmp(ev, "random") == 0) {
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_SEED");
+ if (ev) {
+ sInterruptSeed = atoi(ev);
+ }
+ srandom(sInterruptSeed);
+ sInterruptMode = ModeRandom;
+ } else
+#endif
+ if (PL_strcasecmp(ev, "counter") == 0) {
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_FREQUENCY");
+ if (ev) {
+ sInterruptMaxCounter = atoi(ev);
+ }
+ sInterruptCounter = 0;
+ sInterruptMode = ModeCounter;
+ }
+ }
+ ev = PR_GetEnv("GECKO_REFLOW_INTERRUPT_CHECKS_TO_SKIP");
+ if (ev) {
+ sInterruptChecksToSkip = atoi(ev);
+ }
+
+ ev = PR_GetEnv("GECKO_REFLOW_MIN_NOINTERRUPT_DURATION");
+ int duration_ms = ev ? atoi(ev) : 100;
+ sInterruptTimeout = TimeDuration::FromMilliseconds(duration_ms);
+}
+
+bool nsPresContext::HavePendingInputEvent() {
+ switch (sInterruptMode) {
+#ifndef XP_WIN
+ case ModeRandom:
+ return (random() & 1);
+#endif
+ case ModeCounter:
+ if (sInterruptCounter < sInterruptMaxCounter) {
+ ++sInterruptCounter;
+ return false;
+ }
+ sInterruptCounter = 0;
+ return true;
+ default:
+ case ModeEvent: {
+ nsIFrame* f = PresShell()->GetRootFrame();
+ if (f) {
+ nsIWidget* w = f->GetNearestWidget();
+ if (w) {
+ return w->HasPendingInputEvent();
+ }
+ }
+ return false;
+ }
+ }
+}
+
+bool nsPresContext::HasPendingRestyleOrReflow() {
+ mozilla::PresShell* presShell = PresShell();
+ return presShell->NeedStyleFlush() || presShell->HasPendingReflow();
+}
+
+void nsPresContext::ReflowStarted(bool aInterruptible) {
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ if (!aInterruptible) {
+ printf("STARTING NONINTERRUPTIBLE REFLOW\n");
+ }
+#endif
+ // We don't support interrupting in paginated contexts, since page
+ // sequences only handle initial reflow
+ mInterruptsEnabled = aInterruptible && !IsPaginated() &&
+ StaticPrefs::layout_interruptible_reflow_enabled();
+
+ // Don't set mHasPendingInterrupt based on HavePendingInputEvent() here. If
+ // we ever change that, then we need to update the code in
+ // PresShell::DoReflow to only add the just-reflown root to dirty roots if
+ // it's actually dirty. Otherwise we can end up adding a root that has no
+ // interruptible descendants, just because we detected an interrupt at reflow
+ // start.
+ mHasPendingInterrupt = false;
+
+ mInterruptChecksToSkip = sInterruptChecksToSkip;
+
+ if (mInterruptsEnabled) {
+ mReflowStartTime = TimeStamp::Now();
+ }
+}
+
+bool nsPresContext::CheckForInterrupt(nsIFrame* aFrame) {
+ if (mHasPendingInterrupt) {
+ mPresShell->FrameNeedsToContinueReflow(aFrame);
+ return true;
+ }
+
+ if (!sGotInterruptEnv) {
+ sGotInterruptEnv = true;
+ GetInterruptEnv();
+ }
+
+ if (!mInterruptsEnabled) {
+ return false;
+ }
+
+ if (mInterruptChecksToSkip > 0) {
+ --mInterruptChecksToSkip;
+ return false;
+ }
+ mInterruptChecksToSkip = sInterruptChecksToSkip;
+
+ // Don't interrupt if it's been less than sInterruptTimeout since we started
+ // the reflow.
+ mHasPendingInterrupt =
+ TimeStamp::Now() - mReflowStartTime > sInterruptTimeout &&
+ HavePendingInputEvent() && !IsChrome();
+
+ if (mPendingInterruptFromTest) {
+ mPendingInterruptFromTest = false;
+ mHasPendingInterrupt = true;
+ }
+
+ if (mHasPendingInterrupt) {
+#ifdef NOISY_INTERRUPTIBLE_REFLOW
+ printf("*** DETECTED pending interrupt (time=%lld)\n", PR_Now());
+#endif /* NOISY_INTERRUPTIBLE_REFLOW */
+ mPresShell->FrameNeedsToContinueReflow(aFrame);
+ }
+ return mHasPendingInterrupt;
+}
+
+nsIFrame* nsPresContext::GetPrimaryFrameFor(nsIContent* aContent) {
+ MOZ_ASSERT(aContent, "Don't do that");
+ if (GetPresShell() &&
+ GetPresShell()->GetDocument() == aContent->GetComposedDoc()) {
+ return aContent->GetPrimaryFrame();
+ }
+ return nullptr;
+}
+
+size_t nsPresContext::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
+ // Measurement may be added later if DMD finds it is worthwhile.
+ return 0;
+}
+
+bool nsPresContext::IsRootContentDocumentInProcess() const {
+ if (mDocument->IsResourceDoc()) {
+ return false;
+ }
+ if (IsChrome()) {
+ return false;
+ }
+ // We may not have a root frame, so use views.
+ nsView* view = PresShell()->GetViewManager()->GetRootView();
+ if (!view) {
+ return false;
+ }
+ view = view->GetParent(); // anonymous inner view
+ if (!view) {
+ return true;
+ }
+ view = view->GetParent(); // subdocumentframe's view
+ if (!view) {
+ return true;
+ }
+
+ nsIFrame* f = view->GetFrame();
+ return (f && f->PresContext()->IsChrome());
+}
+
+bool nsPresContext::IsRootContentDocumentCrossProcess() const {
+ if (mDocument->IsResourceDoc()) {
+ return false;
+ }
+
+ if (BrowsingContext* browsingContext = mDocument->GetBrowsingContext()) {
+ if (browsingContext->GetEmbeddedInContentDocument()) {
+ return false;
+ }
+ }
+ return mDocument->IsTopLevelContentDocument();
+}
+
+void nsPresContext::NotifyNonBlankPaint() {
+ MOZ_ASSERT(!mHadNonBlankPaint);
+ mHadNonBlankPaint = true;
+ if (IsRootContentDocumentCrossProcess()) {
+ RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+ if (timing && !IsPrintingOrPrintPreview()) {
+ timing->NotifyNonBlankPaintForRootContentDocument();
+ }
+
+ mFirstNonBlankPaintTime = TimeStamp::Now();
+ }
+ if (IsChrome() && IsRoot()) {
+ if (nsCOMPtr<nsIWidget> rootWidget = GetRootWidget()) {
+ rootWidget->DidGetNonBlankPaint();
+ }
+ }
+}
+
+bool nsPresContext::HasStoppedGeneratingLCP() const {
+ if (auto* perf = GetPerformanceMainThread()) {
+ return perf->HasDispatchedInputEvent() || perf->HasDispatchedScrollEvent();
+ }
+
+ return true;
+}
+
+void nsPresContext::NotifyContentfulPaint() {
+ if (mHadFirstContentfulPaint && HasStoppedGeneratingLCP()) {
+ return;
+ }
+ nsRootPresContext* rootPresContext = GetRootPresContext();
+ if (!rootPresContext) {
+ return;
+ }
+ if (!mHadNonTickContentfulPaint) {
+#ifdef MOZ_WIDGET_ANDROID
+ (new AsyncEventDispatcher(mDocument, u"MozFirstContentfulPaint"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes))
+ ->PostDOMEvent();
+#endif
+ }
+ if (!rootPresContext->RefreshDriver()->IsInRefresh()) {
+ if (!mHadNonTickContentfulPaint) {
+ rootPresContext->RefreshDriver()
+ ->AddForceNotifyContentfulPaintPresContext(this);
+ mHadNonTickContentfulPaint = true;
+ }
+ return;
+ }
+
+ if (!mHadFirstContentfulPaint) {
+ mHadFirstContentfulPaint = true;
+ mFirstContentfulPaintTransactionId =
+ Some(rootPresContext->mRefreshDriver->LastTransactionId().Next());
+ }
+
+ if (auto* perf = GetPerformanceMainThread()) {
+ mMarkPaintTimingStart = TimeStamp::Now();
+ MOZ_ASSERT(rootPresContext->RefreshDriver()->IsInRefresh(),
+ "We should only notify contentful paint during refresh "
+ "driver ticks");
+ if (!perf->HadFCPTimingEntry()) {
+ TimeStamp nowTime = rootPresContext->RefreshDriver()->MostRecentRefresh(
+ /* aEnsureTimerStarted */ false);
+ MOZ_ASSERT(!nowTime.IsNull(),
+ "Most recent refresh timestamp should exist since we are in "
+ "a refresh driver tick");
+ RefPtr<PerformancePaintTiming> paintTiming = new PerformancePaintTiming(
+ perf, u"first-contentful-paint"_ns, nowTime);
+ perf->SetFCPTimingEntry(paintTiming);
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+ if (timing) {
+ TimeStamp navigationStart = timing->GetNavigationStartTimeStamp();
+ TimeDuration elapsed = nowTime - navigationStart;
+ nsIURI* docURI = Document()->GetDocumentURI();
+ nsPrintfCString marker(
+ "Contentful paint after %dms for URL %s",
+ int(elapsed.ToMilliseconds()),
+ nsContentUtils::TruncatedURLForDisplay(docURI).get());
+ PROFILER_MARKER_TEXT(
+ "FirstContentfulPaint", DOM,
+ MarkerOptions(
+ MarkerTiming::Interval(navigationStart, nowTime),
+ MarkerInnerWindowId(mDocument->GetInnerWindow()->WindowID())),
+ marker);
+ }
+ }
+ }
+
+ perf->ProcessElementTiming();
+ }
+}
+
+void nsPresContext::NotifyPaintStatusReset() {
+ mHadNonBlankPaint = false;
+ mHadFirstContentfulPaint = false;
+#if defined(MOZ_WIDGET_ANDROID)
+ (new AsyncEventDispatcher(mDocument, u"MozPaintStatusReset"_ns,
+ CanBubble::eYes, ChromeOnlyDispatch::eYes))
+ ->PostDOMEvent();
+#endif
+ mHadNonTickContentfulPaint = false;
+}
+
+void nsPresContext::NotifyDOMContentFlushed() {
+ NS_ENSURE_TRUE_VOID(mPresShell);
+ if (IsRootContentDocumentCrossProcess()) {
+ RefPtr<nsDOMNavigationTiming> timing = mDocument->GetNavigationTiming();
+ if (timing) {
+ timing->NotifyDOMContentFlushedForRootContentDocument();
+ }
+ }
+}
+
+nscoord nsPresContext::GfxUnitsToAppUnits(gfxFloat aGfxUnits) const {
+ return mDeviceContext->GfxUnitsToAppUnits(aGfxUnits);
+}
+
+gfxFloat nsPresContext::AppUnitsToGfxUnits(nscoord aAppUnits) const {
+ return mDeviceContext->AppUnitsToGfxUnits(aAppUnits);
+}
+
+nscoord nsPresContext::PhysicalMillimetersToAppUnits(float aMM) const {
+ float inches = aMM / MM_PER_INCH_FLOAT;
+ return NSToCoordFloorClamped(
+ inches * float(DeviceContext()->AppUnitsPerPhysicalInch()));
+}
+
+uint64_t nsPresContext::GetRestyleGeneration() const {
+ if (!mRestyleManager) {
+ return 0;
+ }
+ return mRestyleManager->GetRestyleGeneration();
+}
+
+uint64_t nsPresContext::GetUndisplayedRestyleGeneration() const {
+ if (!mRestyleManager) {
+ return 0;
+ }
+ return mRestyleManager->GetUndisplayedRestyleGeneration();
+}
+
+mozilla::intl::Bidi& nsPresContext::BidiEngine() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mBidiEngine) {
+ mBidiEngine = MakeUnique<mozilla::intl::Bidi>();
+ }
+ return *mBidiEngine;
+}
+
+void nsPresContext::FlushFontFeatureValues() {
+ if (!mPresShell) {
+ return; // we've been torn down
+ }
+
+ if (!mFontFeatureValuesDirty) {
+ return;
+ }
+
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ mFontFeatureValuesLookup = styleSet->BuildFontFeatureValueSet();
+ mFontFeatureValuesDirty = false;
+}
+
+void nsPresContext::FlushFontPaletteValues() {
+ if (!mPresShell) {
+ return; // we've been torn down
+ }
+
+ if (!mFontPaletteValuesDirty) {
+ return;
+ }
+
+ ServoStyleSet* styleSet = mPresShell->StyleSet();
+ mFontPaletteValueSet = styleSet->BuildFontPaletteValueSet();
+ mFontPaletteValuesDirty = false;
+
+ if (mFontPaletteCache) {
+ mFontPaletteCache->SetPaletteValueSet(mFontPaletteValueSet);
+ }
+
+ // Even if we're not reflowing anything, a change to the palette means we
+ // need to repaint in order to show the new colors.
+ InvalidatePaintedLayers();
+}
+
+gfx::PaletteCache& nsPresContext::FontPaletteCache() {
+ if (!mFontPaletteCache) {
+ mFontPaletteCache = MakeUnique<gfx::PaletteCache>(mFontPaletteValueSet);
+ }
+ return *mFontPaletteCache.get();
+}
+
+void nsPresContext::SetVisibleArea(const nsRect& r) {
+ if (!r.IsEqualEdges(mVisibleArea)) {
+ mVisibleArea = r;
+ mSizeForViewportUnits = mVisibleArea.Size();
+ if (IsRootContentDocumentCrossProcess()) {
+ AdjustSizeForViewportUnits();
+ }
+ // Visible area does not affect media queries when paginated.
+ if (!IsRootPaginatedDocument()) {
+ MediaFeatureValuesChanged(
+ {mozilla::MediaFeatureChangeReason::ViewportChange},
+ MediaFeatureChangePropagation::JustThisDocument);
+ }
+ }
+}
+
+void nsPresContext::SetDynamicToolbarMaxHeight(ScreenIntCoord aHeight) {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+
+ if (mDynamicToolbarMaxHeight == aHeight) {
+ return;
+ }
+ mDynamicToolbarMaxHeight = aHeight;
+ mDynamicToolbarHeight = aHeight;
+
+ AdjustSizeForViewportUnits();
+
+ if (RefPtr<mozilla::PresShell> presShell = mPresShell) {
+ // Changing the max height of the dynamic toolbar changes the ICB size, we
+ // need to kick a reflow with the current window dimensions since the max
+ // height change doesn't change the window dimensions but
+ // PresShell::ResizeReflow ends up subtracting the new dynamic toolbar
+ // height from the window dimensions and kick a reflow with the proper ICB
+ // size.
+ presShell->ForceResizeReflowWithCurrentDimensions();
+ }
+}
+
+void nsPresContext::AdjustSizeForViewportUnits() {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+ if (mVisibleArea.height == NS_UNCONSTRAINEDSIZE) {
+ // Ignore `NS_UNCONSTRAINEDSIZE` since it's a temporary state during a
+ // reflow. We will end up calling this function again with a proper size in
+ // the same reflow.
+ return;
+ }
+
+ if (MOZ_UNLIKELY(mVisibleArea.height +
+ NSIntPixelsToAppUnits(mDynamicToolbarMaxHeight,
+ mCurAppUnitsPerDevPixel) >
+ nscoord_MAX)) {
+ MOZ_ASSERT_UNREACHABLE("The dynamic toolbar max height is probably wrong");
+ return;
+ }
+
+ mSizeForViewportUnits.height =
+ mVisibleArea.height +
+ NSIntPixelsToAppUnits(mDynamicToolbarMaxHeight, mCurAppUnitsPerDevPixel);
+}
+
+void nsPresContext::UpdateDynamicToolbarOffset(ScreenIntCoord aOffset) {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+ if (!mPresShell) {
+ return;
+ }
+
+ if (!HasDynamicToolbar()) {
+ return;
+ }
+
+ MOZ_ASSERT(-mDynamicToolbarMaxHeight <= aOffset && aOffset <= 0);
+ if (mDynamicToolbarHeight == mDynamicToolbarMaxHeight + aOffset) {
+ return;
+ }
+
+ // Forcibly flush position:fixed elements in the case where the dynamic
+ // toolbar is going to be completely hidden or starts to be visible so that
+ // %-based style values will be recomputed with the visual viewport size which
+ // is including the area covered by the dynamic toolbar.
+ if (mDynamicToolbarHeight == 0 || aOffset == -mDynamicToolbarMaxHeight) {
+ mPresShell->MarkFixedFramesForReflow(IntrinsicDirty::None);
+ mPresShell->AddResizeEventFlushObserverIfNeeded();
+ }
+
+ mDynamicToolbarHeight = mDynamicToolbarMaxHeight + aOffset;
+
+ if (RefPtr<MobileViewportManager> mvm =
+ mPresShell->GetMobileViewportManager()) {
+ mvm->UpdateVisualViewportSizeByDynamicToolbar(-aOffset);
+ }
+
+ mPresShell->StyleSet()->InvalidateForViewportUnits(
+ ServoStyleSet::OnlyDynamic::Yes);
+}
+
+DynamicToolbarState nsPresContext::GetDynamicToolbarState() const {
+ if (!IsRootContentDocumentCrossProcess() || !HasDynamicToolbar()) {
+ return DynamicToolbarState::None;
+ }
+
+ if (mDynamicToolbarMaxHeight == mDynamicToolbarHeight) {
+ return DynamicToolbarState::Expanded;
+ } else if (mDynamicToolbarHeight == 0) {
+ return DynamicToolbarState::Collapsed;
+ }
+ return DynamicToolbarState::InTransition;
+}
+
+void nsPresContext::SetSafeAreaInsets(const ScreenIntMargin& aSafeAreaInsets) {
+ if (mSafeAreaInsets == aSafeAreaInsets) {
+ return;
+ }
+ mSafeAreaInsets = aSafeAreaInsets;
+
+ PostRebuildAllStyleDataEvent(nsChangeHint(0),
+ RestyleHint::RecascadeSubtree());
+}
+
+PerformanceMainThread* nsPresContext::GetPerformanceMainThread() const {
+ if (nsPIDOMWindowInner* innerWindow = mDocument->GetInnerWindow()) {
+ if (auto* perf = static_cast<PerformanceMainThread*>(
+ innerWindow->GetPerformance())) {
+ return perf;
+ }
+ }
+ return nullptr;
+}
+
+void nsPresContext::DoUpdateHiddenByContentVisibilityForAnimations() {
+ MOZ_ASSERT(NeedsToUpdateHiddenByContentVisibilityForAnimations());
+ mNeedsToUpdateHiddenByContentVisibilityForAnimations = false;
+ mDocument->UpdateHiddenByContentVisibilityForAnimations();
+ TimelineManager()->UpdateHiddenByContentVisibilityForAnimations();
+}
+
+#ifdef DEBUG
+
+void nsPresContext::ValidatePresShellAndDocumentReleation() const {
+ NS_ASSERTION(!mPresShell || !mPresShell->GetDocument() ||
+ mPresShell->GetDocument() == mDocument,
+ "nsPresContext doesn't have the same document as nsPresShell!");
+}
+
+#endif // #ifdef DEBUG
+
+nsRootPresContext::nsRootPresContext(dom::Document* aDocument,
+ nsPresContextType aType)
+ : nsPresContext(aDocument, aType) {}
+
+void nsRootPresContext::AddWillPaintObserver(nsIRunnable* aRunnable) {
+ if (!mWillPaintFallbackEvent.IsPending()) {
+ mWillPaintFallbackEvent = new RunWillPaintObservers(this);
+ Document()->Dispatch(do_AddRef(mWillPaintFallbackEvent));
+ }
+ mWillPaintObservers.AppendElement(aRunnable);
+}
+
+/**
+ * Run all runnables that need to get called before the next paint.
+ */
+void nsRootPresContext::FlushWillPaintObservers() {
+ mWillPaintFallbackEvent = nullptr;
+ nsTArray<nsCOMPtr<nsIRunnable>> observers = std::move(mWillPaintObservers);
+ for (uint32_t i = 0; i < observers.Length(); ++i) {
+ observers[i]->Run();
+ }
+}
+
+size_t nsRootPresContext::SizeOfExcludingThis(
+ MallocSizeOf aMallocSizeOf) const {
+ return nsPresContext::SizeOfExcludingThis(aMallocSizeOf);
+
+ // Measurement of the following members may be added later if DMD finds it is
+ // worthwhile:
+ // - mWillPaintObservers
+ // - mWillPaintFallbackEvent
+}
diff --git a/layout/base/nsPresContext.h b/layout/base/nsPresContext.h
new file mode 100644
index 0000000000..bc4e70dbcf
--- /dev/null
+++ b/layout/base/nsPresContext.h
@@ -0,0 +1,1461 @@
+/* -*- 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 presentation of a document, part 1 */
+
+#ifndef nsPresContext_h___
+#define nsPresContext_h___
+
+#include "mozilla/intl/Bidi.h"
+#include "mozilla/AppUnits.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/DepthOrderedFrameList.h"
+#include "mozilla/EnumeratedArray.h"
+#include "mozilla/MediaEmulationData.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/PreferenceSheet.h"
+#include "mozilla/PresShellForwards.h"
+#include "mozilla/ScrollStyles.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WeakPtr.h"
+#include "mozilla/widget/ThemeChangeKind.h"
+#include "nsColor.h"
+#include "nsCompatibility.h"
+#include "nsCoord.h"
+#include "nsCOMPtr.h"
+#include "nsFontMetrics.h"
+#include "nsHashKeys.h"
+#include "nsRect.h"
+#include "nsStringFwd.h"
+#include "nsTHashSet.h"
+#include "nsTHashtable.h"
+#include "nsAtom.h"
+#include "nsIWidgetListener.h" // for nsSizeMode
+#include "nsGkAtoms.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsChangeHint.h"
+#include "gfxTypes.h"
+#include "gfxRect.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+#include "Units.h"
+
+class nsIPrintSettings;
+class nsDocShell;
+class nsIDocShell;
+class nsITheme;
+class nsITimer;
+class nsIContent;
+class nsIFrame;
+class nsFrameManager;
+class nsAtom;
+class nsIRunnable;
+class gfxFontFamily;
+class gfxFontFeatureValueSet;
+class gfxUserFontEntry;
+class gfxUserFontSet;
+class gfxTextPerfMetrics;
+class nsCSSFontFeatureValuesRule;
+class nsCSSFrameConstructor;
+class nsFontCache;
+class nsTransitionManager;
+class nsAnimationManager;
+class nsRefreshDriver;
+class nsIWidget;
+class nsDeviceContext;
+class gfxMissingFontRecorder;
+
+namespace mozilla {
+class AnimationEventDispatcher;
+class EffectCompositor;
+class Encoding;
+class EventStateManager;
+class CounterStyleManager;
+class ManagedPostRefreshObserver;
+class PresShell;
+class RestyleManager;
+class ServoStyleSet;
+class StaticPresData;
+class TimelineManager;
+struct MediaFeatureChange;
+enum class MediaFeatureChangePropagation : uint8_t;
+enum class ColorScheme : uint8_t;
+namespace layers {
+class ContainerLayer;
+class LayerManager;
+} // namespace layers
+namespace dom {
+class Document;
+class Element;
+class PerformanceMainThread;
+enum class PrefersColorSchemeOverride : uint8_t;
+} // namespace dom
+namespace gfx {
+class FontPaletteValueSet;
+class PaletteCache;
+} // namespace gfx
+} // namespace mozilla
+
+// IDs for the default variable and fixed fonts (not to be changed, see
+// nsFont.h) To be used for Get/SetDefaultFont(). The other IDs in nsFont.h are
+// also supported.
+//
+// kGenericFont_moz_variable
+const uint8_t kPresContext_DefaultVariableFont_ID = 0x00;
+// kGenericFont_moz_fixed
+const uint8_t kPresContext_DefaultFixedFont_ID = 0x01;
+
+#ifdef DEBUG
+struct nsAutoLayoutPhase;
+
+enum class nsLayoutPhase : uint8_t {
+ Paint,
+ DisplayListBuilding, // sometimes a subset of the paint phase
+ Reflow,
+ FrameC,
+ COUNT
+};
+#endif
+
+class nsRootPresContext;
+
+// An interface for presentation contexts. Presentation contexts are
+// objects that provide an outer context for a presentation shell.
+
+class nsPresContext : public nsISupports, public mozilla::SupportsWeakPtr {
+ public:
+ using Encoding = mozilla::Encoding;
+ template <typename T>
+ using NotNull = mozilla::NotNull<T>;
+ template <typename T>
+ using Maybe = mozilla::Maybe<T>;
+ using MediaEmulationData = mozilla::MediaEmulationData;
+
+ typedef mozilla::ScrollStyles ScrollStyles;
+ using TransactionId = mozilla::layers::TransactionId;
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS_FINAL
+ NS_DECL_CYCLE_COLLECTION_CLASS(nsPresContext)
+
+ enum nsPresContextType : uint8_t {
+ eContext_Galley, // unpaginated screen presentation
+ eContext_PrintPreview, // paginated screen presentation
+ eContext_Print, // paginated printer presentation
+ eContext_PageLayout // paginated & editable.
+ };
+
+ nsPresContext(mozilla::dom::Document* aDocument, nsPresContextType aType);
+
+ /**
+ * Initialize the presentation context from a particular device.
+ */
+ nsresult Init(nsDeviceContext* aDeviceContext);
+
+ /**
+ * Initialize the font cache if it hasn't been initialized yet.
+ * (Needed for stylo)
+ */
+ void InitFontCache();
+
+ void UpdateFontCacheUserFonts(gfxUserFontSet* aUserFontSet);
+
+ /**
+ * Return the font visibility level to be applied to this context,
+ * potentially blocking user-installed or non-standard fonts from being
+ * used by web content.
+ * Note that depending on ResistFingerprinting options, the caller may
+ * override this value when resolving CSS <generic-family> keywords.
+ */
+ FontVisibility GetFontVisibility() const { return mFontVisibility; }
+
+ /**
+ * Log a message to the console about a font request being blocked.
+ */
+ void ReportBlockedFontFamily(const mozilla::fontlist::Family& aFamily);
+ void ReportBlockedFontFamily(const gfxFontFamily& aFamily);
+
+ /**
+ * Get the nsFontMetrics that describe the properties of
+ * an nsFont.
+ * @param aFont font description to obtain metrics for
+ */
+ already_AddRefed<nsFontMetrics> GetMetricsFor(
+ const nsFont& aFont, const nsFontMetrics::Params& aParams);
+
+ /**
+ * Notification when a font metrics instance created for this context is
+ * about to be deleted
+ */
+ nsresult FontMetricsDeleted(const nsFontMetrics* aFontMetrics);
+
+ /**
+ * Attempt to free up resources by flushing out any fonts no longer
+ * referenced by anything other than the font cache itself.
+ * @return error status
+ */
+ nsresult FlushFontCache();
+
+ /**
+ * Set and detach presentation shell that this context is bound to.
+ * A presentation context may only be bound to a single shell.
+ */
+ void AttachPresShell(mozilla::PresShell* aPresShell);
+ void DetachPresShell();
+
+ nsPresContextType Type() const { return mType; }
+
+ /**
+ * Get the PresentationShell that this context is bound to.
+ */
+ mozilla::PresShell* PresShell() const {
+ NS_ASSERTION(mPresShell, "Null pres shell");
+ return mPresShell;
+ }
+
+ mozilla::PresShell* GetPresShell() const { return mPresShell; }
+
+ void DocumentCharSetChanged(NotNull<const Encoding*> aCharSet);
+
+ mozilla::dom::PerformanceMainThread* GetPerformanceMainThread() const;
+ /**
+ * Returns the parent prescontext for this one. Returns null if this is a
+ * root.
+ */
+ nsPresContext* GetParentPresContext() const;
+
+ /**
+ * Returns the prescontext of the root content document in the same process
+ * that contains this presentation, or null if there isn't one.
+ */
+ nsPresContext* GetInProcessRootContentDocumentPresContext();
+
+ /**
+ * Returns the nearest widget for the root frame or view of this.
+ *
+ * @param aOffset If non-null the offset from the origin of the root
+ * frame's view to the widget's origin (usually positive)
+ * expressed in appunits of this will be returned in
+ * aOffset.
+ */
+ nsIWidget* GetNearestWidget(nsPoint* aOffset = nullptr);
+
+ /**
+ * Returns the root widget for this.
+ */
+ nsIWidget* GetRootWidget() const;
+
+ /**
+ * Returns the widget which may have native focus and handles text input
+ * like keyboard input, IME, etc.
+ */
+ nsIWidget* GetTextInputHandlingWidget() const {
+ // Currently, root widget for each PresContext handles text input.
+ return GetRootWidget();
+ }
+
+ /**
+ * Return the presentation context for the root of the view manager
+ * hierarchy that contains this presentation context, or nullptr if it can't
+ * be found (e.g. it's detached).
+ */
+ nsRootPresContext* GetRootPresContext() const;
+
+ virtual bool IsRoot() const { return false; }
+
+ mozilla::dom::Document* Document() const {
+#ifdef DEBUG
+ ValidatePresShellAndDocumentReleation();
+#endif // #ifdef DEBUG
+ return mDocument;
+ }
+
+ inline mozilla::ServoStyleSet* StyleSet() const;
+
+ bool HasPendingMediaQueryUpdates() const {
+ return !!mPendingMediaFeatureValuesChange;
+ }
+
+ inline nsCSSFrameConstructor* FrameConstructor() const;
+
+ mozilla::AnimationEventDispatcher* AnimationEventDispatcher() {
+ return mAnimationEventDispatcher;
+ }
+
+ mozilla::EffectCompositor* EffectCompositor() { return mEffectCompositor; }
+ nsTransitionManager* TransitionManager() { return mTransitionManager.get(); }
+ nsAnimationManager* AnimationManager() { return mAnimationManager.get(); }
+ const nsAnimationManager* AnimationManager() const {
+ return mAnimationManager.get();
+ }
+ mozilla::TimelineManager* TimelineManager() { return mTimelineManager.get(); }
+
+ nsRefreshDriver* RefreshDriver() { return mRefreshDriver; }
+
+ mozilla::RestyleManager* RestyleManager() {
+ MOZ_ASSERT(mRestyleManager);
+ return mRestyleManager.get();
+ }
+
+ mozilla::CounterStyleManager* CounterStyleManager() const {
+ return mCounterStyleManager;
+ }
+
+ /**
+ * Rebuilds all style data by throwing out the old rule tree and
+ * building a new one, and additionally applying a change hint (which must not
+ * contain nsChangeHint_ReconstructFrame) to the root frame.
+ *
+ * For the restyle hint argument, see RestyleManager::RebuildAllStyleData.
+ * Also rebuild the user font set and counter style manager.
+ *
+ * FIXME(emilio): The name of this is an utter lie. We should probably call
+ * this PostGlobalStyleChange or something, as it doesn't really rebuild
+ * anything unless you tell it to via the change hint / restyle hint
+ * machinery.
+ */
+ void RebuildAllStyleData(nsChangeHint, const mozilla::RestyleHint&);
+ /**
+ * Just like RebuildAllStyleData, except (1) asynchronous and (2) it
+ * doesn't rebuild the user font set / counter-style manager / etc.
+ */
+ void PostRebuildAllStyleDataEvent(nsChangeHint, const mozilla::RestyleHint&);
+
+ void ContentLanguageChanged();
+
+ /** Returns whether any media query changed. */
+ bool FlushPendingMediaFeatureValuesChanged();
+
+ /**
+ * Schedule a media feature change for this document, and potentially for
+ * other subdocuments and images (depending on the arguments).
+ */
+ void MediaFeatureValuesChanged(const mozilla::MediaFeatureChange&,
+ mozilla::MediaFeatureChangePropagation);
+
+ /**
+ * Updates the size mode on all remote children and recursively notifies this
+ * document and all subdocuments (including remote children) that a media
+ * feature value has changed.
+ */
+ void SizeModeChanged(nsSizeMode aSizeMode);
+
+ /**
+ * Access compatibility mode for this context. This is the same as
+ * our document's compatibility mode.
+ */
+ nsCompatibility CompatibilityMode() const;
+
+ /**
+ * Access the image animation mode for this context
+ */
+ uint16_t ImageAnimationMode() const { return mImageAnimationMode; }
+ void SetImageAnimationMode(uint16_t aMode);
+
+ /**
+ * Get medium of presentation
+ */
+ const nsAtom* Medium() const {
+ MOZ_ASSERT(mMedium);
+ return mMediaEmulationData.mMedium ? mMediaEmulationData.mMedium.get()
+ : mMedium;
+ }
+
+ /*
+ * Render the document as if being viewed on a device with the specified
+ * media type.
+ *
+ * If passed null, it stops emulating.
+ */
+ void EmulateMedium(nsAtom* aMediaType);
+
+ const mozilla::PreferenceSheet::Prefs& PrefSheetPrefs() const {
+ return mozilla::PreferenceSheet::PrefsFor(*mDocument);
+ }
+
+ bool ForcingColors() const {
+ return mozilla::PreferenceSheet::MayForceColors() &&
+ !PrefSheetPrefs().mUseDocumentColors;
+ }
+
+ mozilla::ColorScheme DefaultBackgroundColorScheme() const;
+ nscolor DefaultBackgroundColor() const;
+
+ nsISupports* GetContainerWeak() const;
+
+ nsDocShell* GetDocShell() const;
+
+ /**
+ * Get the visible area associated with this presentation context.
+ * This is the size of the visible area that is used for
+ * presenting the document. The returned value is in the standard
+ * nscoord units (as scaled by the device context).
+ */
+ nsRect GetVisibleArea() const { return mVisibleArea; }
+
+ /**
+ * Set the currently visible area. The units for r are standard
+ * nscoord units (as scaled by the device context).
+ */
+ void SetVisibleArea(const nsRect& r);
+
+ nsSize GetSizeForViewportUnits() const { return mSizeForViewportUnits; }
+
+ /**
+ * Set the maximum height of the dynamic toolbar in nscoord units.
+ */
+ MOZ_CAN_RUN_SCRIPT
+ void SetDynamicToolbarMaxHeight(mozilla::ScreenIntCoord aHeight);
+
+ mozilla::ScreenIntCoord GetDynamicToolbarMaxHeight() const {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+ return mDynamicToolbarMaxHeight;
+ }
+
+ /**
+ * Returns true if we are using the dynamic toolbar.
+ */
+ bool HasDynamicToolbar() const {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+ return mDynamicToolbarMaxHeight > 0;
+ }
+
+ /*
+ * |aOffset| must be offset from the bottom edge of the ICB and it's negative.
+ */
+ void UpdateDynamicToolbarOffset(mozilla::ScreenIntCoord aOffset);
+ mozilla::ScreenIntCoord GetDynamicToolbarHeight() const {
+ MOZ_ASSERT(IsRootContentDocumentCrossProcess());
+ return mDynamicToolbarHeight;
+ }
+
+ /**
+ * Returns the state of the dynamic toolbar.
+ */
+ mozilla::DynamicToolbarState GetDynamicToolbarState() const;
+
+ /**
+ * Return true if this presentation context is a paginated
+ * context.
+ */
+ bool IsPaginated() const { return mPaginated; }
+
+ /**
+ * Sets whether the presentation context can scroll for a paginated
+ * context.
+ */
+ void SetPaginatedScrolling(bool aResult);
+
+ /**
+ * Return true if this presentation context can scroll for paginated
+ * context.
+ */
+ bool HasPaginatedScrolling() const { return mCanPaginatedScroll; }
+
+ /**
+ * Get/set the size of a page
+ */
+ const nsSize& GetPageSize() const { return mPageSize; }
+ const nsMargin& GetDefaultPageMargin() const { return mDefaultPageMargin; }
+ void SetPageSize(nsSize aSize) { mPageSize = aSize; }
+
+ /**
+ * Get/set whether this document should be treated as having real pages
+ * XXX This raises the obvious question of why a document that isn't a page
+ * is paginated; there isn't a good reason except history
+ */
+ bool IsRootPaginatedDocument() { return mIsRootPaginatedDocument; }
+ void SetIsRootPaginatedDocument(bool aIsRootPaginatedDocument) {
+ mIsRootPaginatedDocument = aIsRootPaginatedDocument;
+ }
+
+ /**
+ * Get/set the print scaling level; used by nsPageFrame to scale up
+ * pages. Set safe to call before reflow, get guaranteed to be set
+ * properly after reflow.
+ */
+
+ float GetPageScale() { return mPageScale; }
+ void SetPageScale(float aScale) { mPageScale = aScale; }
+
+ /**
+ * Get/set the scaling factor to use when rendering the pages for print
+ * preview. Only safe to get after print preview set up; safe to set anytime.
+ * This is a scaling factor for the display of the print preview. It
+ * does not affect layout. It only affects the size of the onscreen pages
+ * in print preview.
+ *
+ * The getter should only be used by the page sequence frame, which is the
+ * frame responsible for applying the scaling. Other callers should use
+ * nsPageSequenceFrame::GetPrintPreviewScale() if needed, instead of this API.
+ *
+ * XXX Temporary: see http://wiki.mozilla.org/Gecko:PrintPreview
+ */
+ float GetPrintPreviewScaleForSequenceFrameOrScrollbars() const {
+ return mPPScale;
+ }
+ void SetPrintPreviewScale(float aScale) { mPPScale = aScale; }
+
+ nsDeviceContext* DeviceContext() const { return mDeviceContext; }
+ mozilla::EventStateManager* EventStateManager() { return mEventManager; }
+
+ bool UserInputEventsAllowed();
+
+ void MaybeIncreaseMeasuredTicksSinceLoading();
+
+ bool NeedsMoreTicksForUserInput() const;
+
+ void ResetUserInputEventsAllowed() {
+ MOZ_ASSERT(IsRoot());
+ mMeasuredTicksSinceLoading = 0;
+ mUserInputEventsAllowed = false;
+ }
+
+ // Get the text zoom factor in use.
+ float TextZoom() const { return mTextZoom; }
+
+ /**
+ * Notify the pres context that the safe area insets have changed.
+ */
+ void SetSafeAreaInsets(const mozilla::ScreenIntMargin& aInsets);
+
+ mozilla::ScreenIntMargin GetSafeAreaInsets() const { return mSafeAreaInsets; }
+
+ void RegisterManagedPostRefreshObserver(mozilla::ManagedPostRefreshObserver*);
+ void UnregisterManagedPostRefreshObserver(
+ mozilla::ManagedPostRefreshObserver*);
+
+ protected:
+ void CancelManagedPostRefreshObservers();
+
+#ifdef DEBUG
+ void ValidatePresShellAndDocumentReleation() const;
+#endif // #ifdef DEBUG
+
+ void SetTextZoom(float aZoom);
+ void SetFullZoom(float aZoom);
+ void SetOverrideDPPX(float);
+ void SetInRDMPane(bool aInRDMPane);
+
+ public:
+ float GetFullZoom() { return mFullZoom; }
+ /**
+ * Device full zoom differs from full zoom because it gets the zoom from
+ * the device context, which may be using a different zoom due to rounding
+ * of app units to device pixels.
+ */
+ float GetDeviceFullZoom();
+
+ float GetOverrideDPPX() const { return mMediaEmulationData.mDPPX; }
+
+ // Gets the forced color-scheme if any via either our embedder, or DevTools
+ // emulation, or printing.
+ //
+ // NOTE(emilio): This might be called from an stylo thread.
+ Maybe<mozilla::ColorScheme> GetOverriddenOrEmbedderColorScheme() const;
+
+ /**
+ * Recomputes the data dependent on the browsing context, like zoom and text
+ * zoom.
+ */
+ void RecomputeBrowsingContextDependentData();
+
+ /**
+ * Sets the effective color scheme override, and invalidate stuff as needed.
+ */
+ void SetColorSchemeOverride(mozilla::dom::PrefersColorSchemeOverride);
+
+ /**
+ * Return the device's screen size in inches, for font size
+ * inflation.
+ *
+ * If |aChanged| is non-null, then aChanged is filled in with whether
+ * the screen size value has changed since either:
+ * a. the last time the function was called with non-null aChanged, or
+ * b. the first time the function was called.
+ */
+ gfxSize ScreenSizeInchesForFontInflation(bool* aChanged = nullptr);
+
+ int32_t AppUnitsPerDevPixel() const { return mCurAppUnitsPerDevPixel; }
+
+ static nscoord CSSPixelsToAppUnits(int32_t aPixels) {
+ return NSToCoordRoundWithClamp(float(aPixels) *
+ float(mozilla::AppUnitsPerCSSPixel()));
+ }
+
+ static nscoord CSSPixelsToAppUnits(float aPixels) {
+ return NSToCoordRoundWithClamp(aPixels *
+ float(mozilla::AppUnitsPerCSSPixel()));
+ }
+
+ static int32_t AppUnitsToIntCSSPixels(nscoord aAppUnits) {
+ return NSAppUnitsToIntPixels(aAppUnits,
+ float(mozilla::AppUnitsPerCSSPixel()));
+ }
+
+ static float AppUnitsToFloatCSSPixels(nscoord aAppUnits) {
+ return NSAppUnitsToFloatPixels(aAppUnits,
+ float(mozilla::AppUnitsPerCSSPixel()));
+ }
+
+ static double AppUnitsToDoubleCSSPixels(nscoord aAppUnits) {
+ return NSAppUnitsToDoublePixels(aAppUnits,
+ double(mozilla::AppUnitsPerCSSPixel()));
+ }
+
+ nscoord DevPixelsToAppUnits(int32_t aPixels) const {
+ return NSIntPixelsToAppUnits(aPixels, AppUnitsPerDevPixel());
+ }
+
+ int32_t AppUnitsToDevPixels(nscoord aAppUnits) const {
+ return NSAppUnitsToIntPixels(aAppUnits, float(AppUnitsPerDevPixel()));
+ }
+
+ float AppUnitsToFloatDevPixels(nscoord aAppUnits) {
+ return aAppUnits / float(AppUnitsPerDevPixel());
+ }
+
+ int32_t CSSPixelsToDevPixels(int32_t aPixels) {
+ return AppUnitsToDevPixels(CSSPixelsToAppUnits(aPixels));
+ }
+
+ float CSSPixelsToDevPixels(float aPixels) {
+ return NSAppUnitsToFloatPixels(CSSPixelsToAppUnits(aPixels),
+ float(AppUnitsPerDevPixel()));
+ }
+
+ int32_t DevPixelsToIntCSSPixels(int32_t aPixels) {
+ return AppUnitsToIntCSSPixels(DevPixelsToAppUnits(aPixels));
+ }
+
+ static nscoord RoundDownAppUnitsToCSSPixel(nscoord aAppUnits) {
+ return mozilla::RoundDownToMultiple(aAppUnits,
+ mozilla::AppUnitsPerCSSPixel());
+ }
+ static nscoord RoundUpAppUnitsToCSSPixel(nscoord aAppUnits) {
+ return mozilla::RoundUpToMultiple(aAppUnits,
+ mozilla::AppUnitsPerCSSPixel());
+ }
+ static nscoord RoundAppUnitsToCSSPixel(nscoord aAppUnits) {
+ return mozilla::RoundToMultiple(aAppUnits, mozilla::AppUnitsPerCSSPixel());
+ }
+
+ nscoord RoundDownAppUnitsToDevPixel(nscoord aAppUnits) const {
+ return mozilla::RoundDownToMultiple(aAppUnits, AppUnitsPerDevPixel());
+ }
+ nscoord RoundUpAppUnitsToDevPixel(nscoord aAppUnits) const {
+ return mozilla::RoundUpToMultiple(aAppUnits, AppUnitsPerDevPixel());
+ }
+ nscoord RoundAppUnitsToDevPixel(nscoord aAppUnits) const {
+ return mozilla::RoundToMultiple(aAppUnits, AppUnitsPerDevPixel());
+ }
+
+ mozilla::CSSIntPoint DevPixelsToIntCSSPixels(
+ const mozilla::LayoutDeviceIntPoint& aPoint) {
+ return mozilla::CSSIntPoint(
+ AppUnitsToIntCSSPixels(DevPixelsToAppUnits(aPoint.x)),
+ AppUnitsToIntCSSPixels(DevPixelsToAppUnits(aPoint.y)));
+ }
+
+ float DevPixelsToFloatCSSPixels(int32_t aPixels) const {
+ return AppUnitsToFloatCSSPixels(DevPixelsToAppUnits(aPixels));
+ }
+
+ mozilla::CSSToLayoutDeviceScale CSSToDevPixelScale() const {
+ return mozilla::CSSToLayoutDeviceScale(
+ float(mozilla::AppUnitsPerCSSPixel()) / float(AppUnitsPerDevPixel()));
+ }
+
+ // If there is a remainder, it is rounded to nearest app units.
+ nscoord GfxUnitsToAppUnits(gfxFloat aGfxUnits) const;
+
+ gfxFloat AppUnitsToGfxUnits(nscoord aAppUnits) const;
+
+ gfxRect AppUnitsToGfxUnits(const nsRect& aAppRect) const {
+ return gfxRect(AppUnitsToGfxUnits(aAppRect.x),
+ AppUnitsToGfxUnits(aAppRect.y),
+ AppUnitsToGfxUnits(aAppRect.Width()),
+ AppUnitsToGfxUnits(aAppRect.Height()));
+ }
+
+ static nscoord CSSTwipsToAppUnits(float aTwips) {
+ return NSToCoordRoundWithClamp(mozilla::AppUnitsPerCSSInch() *
+ NS_TWIPS_TO_INCHES(aTwips));
+ }
+
+ // Margin-specific version, since they often need TwipsToAppUnits
+ static nsMargin CSSTwipsToAppUnits(const nsIntMargin& marginInTwips) {
+ return nsMargin(CSSTwipsToAppUnits(float(marginInTwips.top)),
+ CSSTwipsToAppUnits(float(marginInTwips.right)),
+ CSSTwipsToAppUnits(float(marginInTwips.bottom)),
+ CSSTwipsToAppUnits(float(marginInTwips.left)));
+ }
+
+ static nscoord CSSPointsToAppUnits(float aPoints) {
+ return NSToCoordRound(aPoints * mozilla::AppUnitsPerCSSInch() /
+ POINTS_PER_INCH_FLOAT);
+ }
+
+ nscoord PhysicalMillimetersToAppUnits(float aMM) const;
+
+ nscoord RoundAppUnitsToNearestDevPixels(nscoord aAppUnits) const {
+ return DevPixelsToAppUnits(AppUnitsToDevPixels(aAppUnits));
+ }
+
+ /**
+ * This checks the root element and the HTML BODY, if any, for an "overflow"
+ * property that should be applied to the viewport. If one is found then we
+ * return the element that we took the overflow from (which should then be
+ * treated as "overflow: visible"), and we store the overflow style here.
+ * If the document is in fullscreen, and the fullscreen element is not the
+ * root, the scrollbar of viewport will be suppressed.
+ * @return if scroll was propagated from some content node, the content node
+ * it was propagated from.
+ */
+ mozilla::dom::Element* UpdateViewportScrollStylesOverride();
+
+ /**
+ * Returns the cached result from the last call to
+ * UpdateViewportScrollStylesOverride() -- i.e. return the node
+ * whose scrollbar styles we have propagated to the viewport (or nullptr if
+ * there is no such node).
+ */
+ mozilla::dom::Element* GetViewportScrollStylesOverrideElement() const {
+ return mViewportScrollOverrideElement;
+ }
+
+ const ScrollStyles& GetViewportScrollStylesOverride() const {
+ return mViewportScrollStyles;
+ }
+
+ /**
+ * Check whether the given element would propagate its scrollbar styles to the
+ * viewport in non-paginated mode.
+ */
+ bool ElementWouldPropagateScrollStyles(const mozilla::dom::Element&);
+
+ /**
+ * Methods for controlling the background drawing.
+ */
+ bool GetBackgroundImageDraw() const { return mDrawImageBackground; }
+ bool GetBackgroundColorDraw() const { return mDrawColorBackground; }
+
+ /**
+ * Check if bidi enabled (set depending on the presence of RTL
+ * characters or when default directionality is RTL).
+ * If enabled, we should apply the Unicode Bidi Algorithm
+ *
+ * @lina 07/12/2000
+ */
+ bool BidiEnabled() const;
+
+ /**
+ * Set bidi enabled. This means we should apply the Unicode Bidi Algorithm
+ *
+ * @lina 07/12/2000
+ */
+ void SetBidiEnabled() const;
+
+ /**
+ * Set visual or implicit mode into the pres context.
+ *
+ * Visual directionality is a presentation method that displays text
+ * as if it were a uni-directional, according to the primary display
+ * direction only.
+ *
+ * Implicit directionality is a presentation method in which the
+ * direction is determined by the Bidi algorithm according to the
+ * category of the characters and the category of the adjacent
+ * characters, and according to their primary direction.
+ *
+ * @lina 05/02/2000
+ */
+ void SetVisualMode(bool aIsVisual) { mIsVisual = aIsVisual; }
+
+ /**
+ * Check whether the content should be treated as visual.
+ *
+ * @lina 05/02/2000
+ */
+ bool IsVisualMode() const { return mIsVisual; }
+
+ enum class InteractionType : uint32_t {
+ ClickInteraction,
+ KeyInteraction,
+ MouseMoveInteraction,
+ ScrollInteraction
+ };
+
+ void RecordInteractionTime(InteractionType aType,
+ const mozilla::TimeStamp& aTimeStamp);
+
+ void DisableInteractionTimeRecording() { mInteractionTimeEnabled = false; }
+
+ // Mohamed
+
+ /**
+ * Set the Bidi options for the presentation context
+ */
+ void SetBidi(uint32_t aBidiOptions);
+
+ /**
+ * Get the Bidi options for the presentation context
+ * Not inline so consumers of nsPresContext are not forced to
+ * include Document.
+ */
+ uint32_t GetBidi() const;
+
+ nsITheme* Theme() const MOZ_NONNULL_RETURN;
+
+ void RecomputeTheme();
+
+ bool UseOverlayScrollbars() const;
+
+ /*
+ * Notify the pres context that the theme has changed. An internal switch
+ * means it's one of our Mozilla themes that changed (e.g., Modern to
+ * Classic). Otherwise, the OS is telling us that the native theme for the
+ * platform has changed.
+ */
+ void ThemeChanged(mozilla::widget::ThemeChangeKind);
+
+ /*
+ * Notify the pres context that the resolution of the user interface has
+ * changed. This happens if a window is moved between HiDPI and non-HiDPI
+ * displays, so that the ratio of points to device pixels changes.
+ * The notification happens asynchronously.
+ */
+ void UIResolutionChanged();
+
+ /*
+ * Like UIResolutionChanged() but invalidates values immediately.
+ */
+ void UIResolutionChangedSync();
+
+ /** Printing methods below should only be used for Medium() == print **/
+ void SetPrintSettings(nsIPrintSettings* aPrintSettings);
+
+ nsIPrintSettings* GetPrintSettings() { return mPrintSettings; }
+
+ /* Helper function that ensures that this prescontext is shown in its
+ docshell if it's the most recent prescontext for the docshell. Returns
+ whether the prescontext is now being shown.
+ */
+ bool EnsureVisible();
+
+#ifdef MOZ_REFLOW_PERF
+ void CountReflows(const char* aName, nsIFrame* aFrame);
+#endif
+
+ void ConstructedFrame() { ++mFramesConstructed; }
+ void ReflowedFrame() { ++mFramesReflowed; }
+ void TriggeredAnimationRestyle() { ++mAnimationTriggeredRestyles; }
+
+ uint64_t FramesConstructedCount() const { return mFramesConstructed; }
+ uint64_t FramesReflowedCount() const { return mFramesReflowed; }
+ uint64_t AnimationTriggeredRestylesCount() const {
+ return mAnimationTriggeredRestyles;
+ }
+
+ static nscoord GetBorderWidthForKeyword(unsigned int aBorderWidthKeyword) {
+ // This table maps border-width enums 'thin', 'medium', 'thick'
+ // to actual nscoord values.
+ static const nscoord kBorderWidths[] = {
+ CSSPixelsToAppUnits(1), CSSPixelsToAppUnits(3), CSSPixelsToAppUnits(5)};
+ MOZ_ASSERT(size_t(aBorderWidthKeyword) <
+ mozilla::ArrayLength(kBorderWidths));
+
+ return kBorderWidths[aBorderWidthKeyword];
+ }
+
+ gfxTextPerfMetrics* GetTextPerfMetrics() { return mTextPerf.get(); }
+
+ bool IsDynamic() const {
+ return mType == eContext_PageLayout || mType == eContext_Galley;
+ }
+ bool IsScreen() const {
+ return mMedium == nsGkAtoms::screen || mType == eContext_PageLayout ||
+ mType == eContext_PrintPreview;
+ }
+ bool IsPrintingOrPrintPreview() const {
+ return mType == eContext_Print || mType == eContext_PrintPreview;
+ }
+
+ bool IsPrintPreview() const { return mType == eContext_PrintPreview; }
+
+ // Is this presentation in a chrome docshell?
+ bool IsChrome() const;
+
+ gfxUserFontSet* GetUserFontSet();
+
+ // Should be called whenever the set of fonts available in the user
+ // font set changes (e.g., because a new font loads, or because the
+ // user font set is changed and fonts become unavailable).
+ void UserFontSetUpdated(gfxUserFontEntry* aUpdatedFont = nullptr);
+
+ gfxMissingFontRecorder* MissingFontRecorder() { return mMissingFonts.get(); }
+
+ void NotifyMissingFonts();
+
+ void FlushCounterStyles();
+ void MarkCounterStylesDirty();
+
+ void FlushFontFeatureValues();
+ void MarkFontFeatureValuesDirty() { mFontFeatureValuesDirty = true; }
+
+ void FlushFontPaletteValues();
+ void MarkFontPaletteValuesDirty() { mFontPaletteValuesDirty = true; }
+
+ mozilla::gfx::PaletteCache& FontPaletteCache();
+
+ // Ensure that it is safe to hand out CSS rules outside the layout
+ // engine by ensuring that all CSS style sheets have unique inners
+ // and, if necessary, synchronously rebuilding all style data.
+ void EnsureSafeToHandOutCSSRules();
+
+ // Mark an area as invalidated, associated with a given transaction id
+ // (allocated by nsRefreshDriver::GetTransactionId). Invalidated regions will
+ // be dispatched to MozAfterPaint events when NotifyDidPaintForSubtree is
+ // called for the transaction id (or any higher id).
+ void NotifyInvalidation(TransactionId aTransactionId, const nsRect& aRect);
+ // aRect is in device pixels
+ void NotifyInvalidation(TransactionId aTransactionId, const nsIntRect& aRect);
+ void NotifyDidPaintForSubtree(
+ TransactionId aTransactionId = TransactionId{0},
+ const mozilla::TimeStamp& aTimeStamp = mozilla::TimeStamp());
+ void NotifyRevokingDidPaint(TransactionId aTransactionId);
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void FireDOMPaintEvent(
+ nsTArray<nsRect>* aList, TransactionId aTransactionId,
+ mozilla::TimeStamp aTimeStamp = mozilla::TimeStamp());
+
+ bool IsDOMPaintEventPending();
+
+ /**
+ * Returns the RestyleManager's restyle generation counter.
+ */
+ uint64_t GetRestyleGeneration() const;
+ uint64_t GetUndisplayedRestyleGeneration() const;
+
+ /**
+ * Returns whether there are any pending restyles or reflows.
+ */
+ bool HasPendingRestyleOrReflow();
+
+ /**
+ * Notify the prescontext that the presshell is about to reflow a reflow root.
+ * The single argument indicates whether this reflow should be interruptible.
+ * If aInterruptible is false then CheckForInterrupt and HasPendingInterrupt
+ * will always return false. If aInterruptible is true then CheckForInterrupt
+ * will return true when a pending event is detected. This is for use by the
+ * presshell only. Reflow code wanting to prevent interrupts should use
+ * InterruptPreventer.
+ */
+ void ReflowStarted(bool aInterruptible);
+
+ /**
+ * A class that can be used to temporarily disable reflow interruption.
+ */
+ class InterruptPreventer;
+ friend class InterruptPreventer;
+ class MOZ_STACK_CLASS InterruptPreventer {
+ public:
+ explicit InterruptPreventer(nsPresContext* aCtx)
+ : mCtx(aCtx),
+ mInterruptsEnabled(aCtx->mInterruptsEnabled),
+ mHasPendingInterrupt(aCtx->mHasPendingInterrupt) {
+ mCtx->mInterruptsEnabled = false;
+ mCtx->mHasPendingInterrupt = false;
+ }
+ ~InterruptPreventer() {
+ mCtx->mInterruptsEnabled = mInterruptsEnabled;
+ mCtx->mHasPendingInterrupt = mHasPendingInterrupt;
+ }
+
+ private:
+ nsPresContext* mCtx;
+ bool mInterruptsEnabled;
+ bool mHasPendingInterrupt;
+ };
+
+ /**
+ * Check for interrupts. This may return true if a pending event is
+ * detected. Once it has returned true, it will keep returning true
+ * until ReflowStarted is called. In all cases where this returns true,
+ * the passed-in frame (which should be the frame whose reflow will be
+ * interrupted if true is returned) will be passed to
+ * PresShell::FrameNeedsToContinueReflow.
+ */
+ bool CheckForInterrupt(nsIFrame* aFrame);
+ /**
+ * Returns true if CheckForInterrupt has returned true since the last
+ * ReflowStarted call. Cannot itself trigger an interrupt check.
+ */
+ bool HasPendingInterrupt() { return mHasPendingInterrupt; }
+ /**
+ * Sets a flag that will trip a reflow interrupt. This only bypasses the
+ * interrupt timeout and the pending event check; other checks such as whether
+ * interrupts are enabled and the interrupt check skipping still take effect.
+ */
+ void SetPendingInterruptFromTest() { mPendingInterruptFromTest = true; }
+
+ /**
+ * If we have a presshell, and if the given content's current
+ * document is the same as our presshell's document, return the
+ * content's primary frame. Otherwise, return null. Only use this
+ * if you care about which presshell the primary frame is in.
+ */
+ nsIFrame* GetPrimaryFrameFor(nsIContent* aContent);
+
+ virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+ virtual size_t SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+ }
+
+ /**
+ * We are a root content document in process if: we are not a resource doc, we
+ * are not chrome, and we either have no parent in the current process or our
+ * parent is chrome.
+ */
+ bool IsRootContentDocumentInProcess() const;
+
+ /**
+ * We are a root content document cross process if: we are not a resource doc,
+ * we are not chrome, and we either have no parent in any process or our
+ * parent is chrome.
+ */
+ bool IsRootContentDocumentCrossProcess() const;
+
+ bool HadNonBlankPaint() const { return mHadNonBlankPaint; }
+ bool HadFirstContentfulPaint() const { return mHadFirstContentfulPaint; }
+ bool HasStoppedGeneratingLCP() const;
+ void NotifyNonBlankPaint();
+ void NotifyContentfulPaint();
+ void NotifyPaintStatusReset();
+ void NotifyDOMContentFlushed();
+
+ bool HasEverBuiltInvisibleText() const { return mHasEverBuiltInvisibleText; }
+ void SetBuiltInvisibleText() { mHasEverBuiltInvisibleText = true; }
+
+ bool HasWarnedAboutTooLargeDashedOrDottedRadius() const {
+ return mHasWarnedAboutTooLargeDashedOrDottedRadius;
+ }
+
+ void SetHasWarnedAboutTooLargeDashedOrDottedRadius() {
+ mHasWarnedAboutTooLargeDashedOrDottedRadius = true;
+ }
+
+ void RegisterContainerQueryFrame(nsIFrame* aFrame);
+ void UnregisterContainerQueryFrame(nsIFrame* aFrame);
+ bool HasContainerQueryFrames() const {
+ return !mContainerQueryFrames.IsEmpty();
+ }
+
+ void FinishedContainerQueryUpdate();
+
+ bool UpdateContainerQueryStyles();
+
+ mozilla::intl::Bidi& BidiEngine();
+
+ gfxFontFeatureValueSet* GetFontFeatureValuesLookup() const {
+ return mFontFeatureValuesLookup;
+ }
+
+ mozilla::gfx::FontPaletteValueSet* GetFontPaletteValueSet() const {
+ return mFontPaletteValueSet;
+ }
+
+ bool NeedsToUpdateHiddenByContentVisibilityForAnimations() const {
+ return mNeedsToUpdateHiddenByContentVisibilityForAnimations;
+ }
+ void SetNeedsToUpdateHiddenByContentVisibilityForAnimations() {
+ mNeedsToUpdateHiddenByContentVisibilityForAnimations = true;
+ }
+ void UpdateHiddenByContentVisibilityForAnimationsIfNeeded() {
+ if (mNeedsToUpdateHiddenByContentVisibilityForAnimations) {
+ DoUpdateHiddenByContentVisibilityForAnimations();
+ }
+ }
+
+ protected:
+ void DoUpdateHiddenByContentVisibilityForAnimations();
+ friend class nsRunnableMethod<nsPresContext>;
+ void ThemeChangedInternal();
+ void RefreshSystemMetrics();
+
+ // Update device context's resolution from the widget
+ void UIResolutionChangedInternal();
+
+ void SetImgAnimations(nsIContent* aParent, uint16_t aMode);
+ void SetSMILAnimations(mozilla::dom::Document* aDoc, uint16_t aNewMode,
+ uint16_t aOldMode);
+
+ static void PreferenceChanged(const char* aPrefName, void* aSelf);
+ void PreferenceChanged(const char* aPrefName);
+
+ void GetUserPreferences();
+
+ void UpdateCharSet(NotNull<const Encoding*> aCharSet);
+
+ void DoForceReflowForFontInfoUpdateFromStyle();
+
+ public:
+ // Used by the PresShell to force a reflow when some aspect of font info
+ // has been updated, potentially affecting font selection and layout.
+ void ForceReflowForFontInfoUpdate(bool aNeedsReframe);
+ void ForceReflowForFontInfoUpdateFromStyle();
+
+ /**
+ * Checks for MozAfterPaint listeners on the document
+ */
+ bool MayHavePaintEventListener();
+
+ void InvalidatePaintedLayers();
+
+ uint32_t GetNextFrameRateMultiplier() const {
+ return mNextFrameRateMultiplier;
+ }
+
+ void DidUseFrameRateMultiplier() {
+ // This heuristic is used to reduce frame rate between fcp and the end of
+ // the page load.
+ if (mNextFrameRateMultiplier < 8) {
+ ++mNextFrameRateMultiplier;
+ }
+ }
+
+ mozilla::TimeStamp GetMarkPaintTimingStart() const {
+ return mMarkPaintTimingStart;
+ }
+
+ protected:
+ // May be called multiple times (unlink, destructor)
+ void Destroy();
+
+ void AppUnitsPerDevPixelChanged();
+
+ bool HavePendingInputEvent();
+
+ // Creates a one-shot timer with the given aCallback & aDelay.
+ // Returns a refcounted pointer to the timer (or nullptr on failure).
+ already_AddRefed<nsITimer> CreateTimer(nsTimerCallbackFunc aCallback,
+ const char* aName, uint32_t aDelay);
+
+ struct TransactionInvalidations {
+ TransactionId mTransactionId;
+ nsTArray<nsRect> mInvalidations;
+ bool mIsWaitingForPreviousTransaction = false;
+ };
+ TransactionInvalidations* GetInvalidations(TransactionId aTransactionId);
+
+ // This should be called only when we update mVisibleArea or
+ // mDynamicToolbarMaxHeight or `app units per device pixels` changes.
+ void AdjustSizeForViewportUnits();
+
+ // Call in response to prefs changes that might affect what fonts should be
+ // visibile to CSS. Returns whether the current visibility value actually
+ // changed (in which case content should be reflowed).
+ bool UpdateFontVisibility();
+ void ReportBlockedFontFamilyName(const nsCString& aFamily,
+ FontVisibility aVisibility);
+
+ // IMPORTANT: The ownership implicit in the following member variables
+ // has been explicitly checked. If you add any members to this class,
+ // please make the ownership explicit (pinkerton, scc).
+
+ // the PresShell owns a strong reference to the nsPresContext, and is
+ // responsible for nulling this pointer before it is destroyed
+ mozilla::PresShell* MOZ_NON_OWNING_REF mPresShell; // [WEAK]
+ RefPtr<mozilla::dom::Document> mDocument;
+ RefPtr<nsDeviceContext> mDeviceContext; // [STRONG] could be weak, but
+ // better safe than sorry.
+ // Cannot reintroduce cycles
+ // since there is no dependency
+ // from gfx back to layout.
+ RefPtr<nsFontCache> mFontCache;
+ RefPtr<mozilla::EventStateManager> mEventManager;
+ RefPtr<nsRefreshDriver> mRefreshDriver;
+ RefPtr<mozilla::AnimationEventDispatcher> mAnimationEventDispatcher;
+ RefPtr<mozilla::EffectCompositor> mEffectCompositor;
+ mozilla::UniquePtr<nsTransitionManager> mTransitionManager;
+ mozilla::UniquePtr<nsAnimationManager> mAnimationManager;
+ mozilla::UniquePtr<mozilla::TimelineManager> mTimelineManager;
+ mozilla::UniquePtr<mozilla::RestyleManager> mRestyleManager;
+ RefPtr<mozilla::CounterStyleManager> mCounterStyleManager;
+ const nsStaticAtom* mMedium;
+ RefPtr<gfxFontFeatureValueSet> mFontFeatureValuesLookup;
+ RefPtr<mozilla::gfx::FontPaletteValueSet> mFontPaletteValueSet;
+
+ mozilla::UniquePtr<mozilla::gfx::PaletteCache> mFontPaletteCache;
+
+ // TODO(emilio): Maybe lazily create and put under a UniquePtr if this grows a
+ // lot?
+ MediaEmulationData mMediaEmulationData;
+
+ float mTextZoom; // Text zoom, defaults to 1.0
+ float mFullZoom; // Page zoom, defaults to 1.0
+ gfxSize mLastFontInflationScreenSize;
+
+ int32_t mCurAppUnitsPerDevPixel;
+ int32_t mAutoQualityMinFontSizePixelsPref;
+
+ nsCOMPtr<nsITheme> mTheme;
+ nsCOMPtr<nsIPrintSettings> mPrintSettings;
+
+ mozilla::UniquePtr<mozilla::intl::Bidi> mBidiEngine;
+
+ AutoTArray<TransactionInvalidations, 4> mTransactions;
+
+ // text performance metrics
+ mozilla::UniquePtr<gfxTextPerfMetrics> mTextPerf;
+
+ mozilla::UniquePtr<gfxMissingFontRecorder> mMissingFonts;
+
+ nsRect mVisibleArea;
+ // This value is used to resolve viewport units.
+ // On mobile this size is including the dynamic toolbar maximum height below.
+ // On desktops this size is pretty much the same as |mVisibleArea|.
+ nsSize mSizeForViewportUnits;
+ // The maximum height of the dynamic toolbar on mobile.
+ mozilla::ScreenIntCoord mDynamicToolbarMaxHeight;
+ mozilla::ScreenIntCoord mDynamicToolbarHeight;
+ // Safe area insets support
+ mozilla::ScreenIntMargin mSafeAreaInsets;
+ nsSize mPageSize;
+
+ // The computed page margins from the print settings.
+ //
+ // This margin will be used for each page in the current print operation, by
+ // default (i.e. unless overridden by @page rules).
+ //
+ // FIXME(emilio): Maybe we could let a global @page rule do that, though it's
+ // sketchy at best, see https://github.com/w3c/csswg-drafts/issues/5437 for
+ // discussion.
+ nsMargin mDefaultPageMargin;
+ float mPageScale;
+ float mPPScale;
+
+ // This is a non-owning pointer. May be null. If non-null, it's guaranteed to
+ // be pointing to an element that's still alive, because we'll reset it in
+ // UpdateViewportScrollStylesOverride() as part of the cleanup code when
+ // this element is removed from the document. (For <body> and the root
+ // element, this call happens in nsCSSFrameConstructor::ContentRemoved(). For
+ // fullscreen elements, it happens in the fullscreen-specific cleanup invoked
+ // by Element::UnbindFromTree().)
+ mozilla::dom::Element* MOZ_NON_OWNING_REF mViewportScrollOverrideElement;
+
+ // Counters for tests and tools that want to detect frame construction
+ // or reflow.
+ uint64_t mElementsRestyled;
+ uint64_t mFramesConstructed;
+ uint64_t mFramesReflowed;
+ uint64_t mAnimationTriggeredRestyles;
+
+ mozilla::TimeStamp mReflowStartTime;
+
+ // Defined in https://w3c.github.io/paint-timing/#mark-paint-timing step 2.
+ mozilla::TimeStamp mMarkPaintTimingStart;
+
+ Maybe<TransactionId> mFirstContentfulPaintTransactionId;
+
+ mozilla::UniquePtr<mozilla::MediaFeatureChange>
+ mPendingMediaFeatureValuesChange;
+
+ // Time of various first interaction types, used to report time from
+ // first paint of the top level content pres shell to first interaction.
+ mozilla::TimeStamp mFirstNonBlankPaintTime;
+ mozilla::TimeStamp mFirstClickTime;
+ mozilla::TimeStamp mFirstKeyTime;
+ mozilla::TimeStamp mFirstMouseMoveTime;
+ mozilla::TimeStamp mFirstScrollTime;
+
+ // last time we did a full style flush
+ mozilla::TimeStamp mLastStyleUpdateForAllAnimations;
+
+ uint32_t mInterruptChecksToSkip;
+
+ // During page load we use slower frame rate.
+ uint32_t mNextFrameRateMultiplier;
+
+ uint32_t mMeasuredTicksSinceLoading;
+
+ nsTArray<RefPtr<mozilla::ManagedPostRefreshObserver>>
+ mManagedPostRefreshObservers;
+
+ // If we block the use of a font-family that is explicitly requested,
+ // due to font visibility settings, we log a message to the web console;
+ // this hash-set keeps track of names we've logged for this context, so
+ // that we can avoid repeatedly reporting the same font.
+ nsTHashSet<nsCString> mBlockedFonts;
+
+ // The set of container query boxes currently in the document, sorted by
+ // depth.
+ mozilla::DepthOrderedFrameList mContainerQueryFrames;
+ // The set of container query elements currently in the document that have
+ // been updated so far. This is necessary to avoid reentering on container
+ // query style changes which cause us to do frame reconstruction.
+ nsTHashSet<nsIContent*> mUpdatedContainerQueryContents;
+
+ ScrollStyles mViewportScrollStyles;
+
+ uint16_t mImageAnimationMode;
+ uint16_t mImageAnimationModePref;
+
+ nsPresContextType mType;
+
+ public:
+ // The following are public member variables so that we can use them
+ // with mozilla::AutoToggle or mozilla::AutoRestore.
+
+ // Should we disable font size inflation because we're inside of
+ // shrink-wrapping calculations on an inflation container?
+ bool mInflationDisabledForShrinkWrap;
+
+ protected:
+ static constexpr size_t kThemeChangeKindBits = 2;
+ static_assert(unsigned(mozilla::widget::ThemeChangeKind::AllBits) <=
+ (1u << kThemeChangeKindBits) - 1,
+ "theme change kind doesn't fit");
+
+ unsigned mInteractionTimeEnabled : 1;
+ unsigned mHasPendingInterrupt : 1;
+ unsigned mHasEverBuiltInvisibleText : 1;
+ unsigned mPendingInterruptFromTest : 1;
+ unsigned mInterruptsEnabled : 1;
+ unsigned mDrawImageBackground : 1;
+ unsigned mDrawColorBackground : 1;
+ unsigned mNeverAnimate : 1;
+ unsigned mPaginated : 1;
+ unsigned mCanPaginatedScroll : 1;
+ unsigned mDoScaledTwips : 1;
+ unsigned mIsRootPaginatedDocument : 1;
+ unsigned mPendingThemeChanged : 1;
+ // widget::ThemeChangeKind
+ unsigned mPendingThemeChangeKind : kThemeChangeKindBits;
+ unsigned mPendingUIResolutionChanged : 1;
+ unsigned mPendingFontInfoUpdateReflowFromStyle : 1;
+
+ // Are we currently drawing an SVG glyph?
+ unsigned mIsGlyph : 1;
+
+ // Is the current mCounterStyleManager valid?
+ unsigned mCounterStylesDirty : 1;
+
+ // Is the current mFontFeatureValuesLookup valid?
+ unsigned mFontFeatureValuesDirty : 1;
+
+ // Is the current mFontFeatureValueSet valid?
+ unsigned mFontPaletteValuesDirty : 1;
+
+ unsigned mIsVisual : 1;
+
+ // Are we in the RDM pane?
+ unsigned mInRDMPane : 1;
+
+ unsigned mHasWarnedAboutTooLargeDashedOrDottedRadius : 1;
+
+ // Have we added quirk.css to the style set?
+ unsigned mQuirkSheetAdded : 1;
+
+ // Has NotifyNonBlankPaint been called on this PresContext?
+ unsigned mHadNonBlankPaint : 1;
+ // Has NotifyContentfulPaint been called on this PresContext?
+ unsigned mHadFirstContentfulPaint : 1;
+ // True when a contentful paint has happened and this paint doesn't
+ // come from the regular tick process. Usually this means a
+ // contentful paint was triggered manually.
+ unsigned mHadNonTickContentfulPaint : 1;
+
+ // Has NotifyDidPaintForSubtree been called for a contentful paint?
+ unsigned mHadContentfulPaintComposite : 1;
+
+ // Whether we might need to update c-v state for animations.
+ unsigned mNeedsToUpdateHiddenByContentVisibilityForAnimations : 1;
+
+ unsigned mUserInputEventsAllowed : 1;
+#ifdef DEBUG
+ unsigned mInitialized : 1;
+#endif
+
+ // FIXME(emilio): These would be better packed on top of the bitfields, but
+ // that breaks bindgen in win32.
+ FontVisibility mFontVisibility = FontVisibility::Unknown;
+ mozilla::dom::PrefersColorSchemeOverride mOverriddenOrEmbedderColorScheme;
+
+ protected:
+ virtual ~nsPresContext();
+
+ void LastRelease();
+
+ void EnsureTheme();
+
+#ifdef DEBUG
+ private:
+ friend struct nsAutoLayoutPhase;
+ mozilla::EnumeratedArray<nsLayoutPhase, nsLayoutPhase::COUNT, uint32_t>
+ mLayoutPhaseCount;
+
+ public:
+ uint32_t LayoutPhaseCount(nsLayoutPhase aPhase) {
+ return mLayoutPhaseCount[aPhase];
+ }
+#endif
+};
+
+class nsRootPresContext final : public nsPresContext {
+ public:
+ nsRootPresContext(mozilla::dom::Document* aDocument, nsPresContextType aType);
+ virtual bool IsRoot() const override { return true; }
+
+ /**
+ * Add a runnable that will get called before the next paint. They will get
+ * run eventually even if painting doesn't happen. They might run well before
+ * painting happens.
+ */
+ void AddWillPaintObserver(nsIRunnable* aRunnable);
+
+ /**
+ * Run all runnables that need to get called before the next paint.
+ */
+ void FlushWillPaintObservers();
+
+ virtual size_t SizeOfExcludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const override;
+
+ protected:
+ class RunWillPaintObservers : public mozilla::Runnable {
+ public:
+ explicit RunWillPaintObservers(nsRootPresContext* aPresContext)
+ : Runnable("nsPresContextType::RunWillPaintObservers"),
+ mPresContext(aPresContext) {}
+ void Revoke() { mPresContext = nullptr; }
+ NS_IMETHOD Run() override {
+ if (mPresContext) {
+ mPresContext->FlushWillPaintObservers();
+ }
+ return NS_OK;
+ }
+ // The lifetime of this reference is handled by an nsRevocableEventPtr
+ nsRootPresContext* MOZ_NON_OWNING_REF mPresContext;
+ };
+
+ friend class nsPresContext;
+
+ nsTArray<nsCOMPtr<nsIRunnable>> mWillPaintObservers;
+ nsRevocableEventPtr<RunWillPaintObservers> mWillPaintFallbackEvent;
+};
+
+#ifdef MOZ_REFLOW_PERF
+
+# define DO_GLOBAL_REFLOW_COUNT(_name) \
+ aPresContext->CountReflows((_name), (nsIFrame*)this);
+#else
+# define DO_GLOBAL_REFLOW_COUNT(_name)
+#endif // MOZ_REFLOW_PERF
+
+#endif /* nsPresContext_h___ */
diff --git a/layout/base/nsPresContextInlines.h b/layout/base/nsPresContextInlines.h
new file mode 100644
index 0000000000..60026f5266
--- /dev/null
+++ b/layout/base/nsPresContextInlines.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 nsPresContextInlines_h
+#define nsPresContextInlines_h
+
+#include "mozilla/dom/Document.h"
+#include "mozilla/PresShell.h"
+#include "nsCSSFrameConstructor.h"
+
+inline mozilla::ServoStyleSet* nsPresContext::StyleSet() const {
+ return mDocument->StyleSetForPresShell();
+}
+
+inline nsCSSFrameConstructor* nsPresContext::FrameConstructor() const {
+ return PresShell()->FrameConstructor();
+}
+
+#endif // #ifndef nsPresContextInlines_h
diff --git a/layout/base/nsQuoteList.cpp b/layout/base/nsQuoteList.cpp
new file mode 100644
index 0000000000..19e4306ec8
--- /dev/null
+++ b/layout/base/nsQuoteList.cpp
@@ -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/. */
+
+/* implementation of quotes for the CSS 'content' property */
+
+#include "nsQuoteList.h"
+#include "nsReadableUtils.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+#include "nsIFrameInlines.h"
+#include "nsContainerFrame.h"
+#include "mozilla/ContainStyleScopeManager.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/Text.h"
+#include "mozilla/intl/Quotes.h"
+
+using namespace mozilla;
+
+bool nsQuoteNode::InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame) {
+ nsGenConNode::InitTextFrame(aList, aPseudoFrame, aTextFrame);
+
+ nsQuoteList* quoteList = static_cast<nsQuoteList*>(aList);
+ bool dirty = false;
+ quoteList->Insert(this);
+ if (quoteList->IsLast(this))
+ quoteList->Calc(this);
+ else
+ dirty = true;
+
+ // Don't set up text for 'no-open-quote' and 'no-close-quote'.
+ if (IsRealQuote()) {
+ aTextFrame->GetContent()->AsText()->SetText(Text(), false);
+ }
+ return dirty;
+}
+
+nsString nsQuoteNode::Text() {
+ NS_ASSERTION(mType == StyleContentType::OpenQuote ||
+ mType == StyleContentType::CloseQuote,
+ "should only be called when mText should be non-null");
+ nsString result;
+ int32_t depth = Depth();
+ MOZ_ASSERT(depth >= -1);
+
+ if (depth < 0) {
+ return result;
+ }
+
+ const auto& quotesProp = mPseudoFrame->StyleList()->mQuotes;
+
+ if (quotesProp.IsAuto()) {
+ // Look up CLDR-derived quotation marks for the language of the context.
+ const nsIFrame* frame = mPseudoFrame->GetInFlowParent();
+ // Parent of the pseudo is the element around which the quotes are applied;
+ // we want lang from *its* parent, unless it is the root.
+ // XXX Are there other cases where we shouldn't look up to the parent?
+ if (!frame->Style()->IsRootElementStyle()) {
+ if (const nsIFrame* parent = frame->GetInFlowParent()) {
+ frame = parent;
+ }
+ }
+ const intl::Quotes* quotes =
+ intl::QuotesForLang(frame->StyleFont()->mLanguage);
+ // If we don't have quote-mark data for the language, use built-in
+ // defaults.
+ if (!quotes) {
+ static const intl::Quotes sDefaultQuotes = {
+ {0x201c, 0x201d, 0x2018, 0x2019}};
+ quotes = &sDefaultQuotes;
+ }
+ size_t index = (depth == 0 ? 0 : 2); // select first or second pair
+ index += (mType == StyleContentType::OpenQuote ? 0 : 1); // open or close
+ result.Append(quotes->mChars[index]);
+ return result;
+ }
+
+ MOZ_ASSERT(quotesProp.IsQuoteList());
+ const Span<const StyleQuotePair> quotes = quotesProp.AsQuoteList().AsSpan();
+
+ // Reuse the last pair when the depth is greater than the number of
+ // pairs of quotes. (Also make 'quotes: none' and close-quote from
+ // a depth of 0 equivalent for the next test.)
+ if (depth >= static_cast<int32_t>(quotes.Length())) {
+ depth = static_cast<int32_t>(quotes.Length()) - 1;
+ }
+
+ if (depth == -1) {
+ // close-quote from a depth of 0 or 'quotes: none'
+ return result;
+ }
+
+ const StyleQuotePair& pair = quotes[depth];
+ const StyleOwnedStr& quote =
+ mType == StyleContentType::OpenQuote ? pair.opening : pair.closing;
+ result.Assign(NS_ConvertUTF8toUTF16(quote.AsString()));
+ return result;
+}
+
+static int32_t GetDepthBeforeFirstQuoteNode(ContainStyleScope* aScope) {
+ for (auto* ancestor = aScope->GetParent(); ancestor;
+ ancestor = ancestor->GetParent()) {
+ auto& quoteList = ancestor->GetQuoteList();
+ if (auto* node = static_cast<nsQuoteNode*>(
+ aScope->GetPrecedingElementInGenConList(&quoteList))) {
+ return node->DepthAfter();
+ }
+ }
+ return 0;
+}
+
+void nsQuoteList::Calc(nsQuoteNode* aNode) {
+ if (aNode == FirstNode()) {
+ aNode->mDepthBefore = GetDepthBeforeFirstQuoteNode(mScope);
+ } else {
+ aNode->mDepthBefore = Prev(aNode)->DepthAfter();
+ }
+}
+
+void nsQuoteList::RecalcAll() {
+ for (nsQuoteNode* node = FirstNode(); node; node = Next(node)) {
+ int32_t oldDepth = node->mDepthBefore;
+ Calc(node);
+
+ if (node->mDepthBefore != oldDepth && node->mText && node->IsRealQuote())
+ node->mText->SetData(node->Text(), IgnoreErrors());
+ }
+}
+
+#ifdef DEBUG
+void nsQuoteList::PrintChain() {
+ using StyleContentType = nsQuoteNode::StyleContentType;
+
+ printf("Chain: \n");
+ for (nsQuoteNode* node = FirstNode(); node; node = Next(node)) {
+ printf(" %p %d - ", static_cast<void*>(node), node->mDepthBefore);
+ switch (node->mType) {
+ case StyleContentType::OpenQuote:
+ printf("open");
+ break;
+ case StyleContentType::NoOpenQuote:
+ printf("noOpen");
+ break;
+ case StyleContentType::CloseQuote:
+ printf("close");
+ break;
+ case StyleContentType::NoCloseQuote:
+ printf("noClose");
+ break;
+ default:
+ printf("unknown!!!");
+ }
+ printf(" %d - %d,", node->Depth(), node->DepthAfter());
+ if (node->mText) {
+ nsAutoString data;
+ node->mText->GetData(data);
+ printf(" \"%s\",", NS_ConvertUTF16toUTF8(data).get());
+ }
+ printf("\n");
+ }
+}
+#endif
diff --git a/layout/base/nsQuoteList.h b/layout/base/nsQuoteList.h
new file mode 100644
index 0000000000..a2a913cec8
--- /dev/null
+++ b/layout/base/nsQuoteList.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/. */
+
+/* implementation of quotes for the CSS 'content' property */
+
+#ifndef nsQuoteList_h___
+#define nsQuoteList_h___
+
+#include "mozilla/Attributes.h"
+#include "nsGenConList.h"
+
+namespace mozilla {
+
+class ContainStyleScope;
+
+} // namespace mozilla
+
+struct nsQuoteNode : public nsGenConNode {
+ // open-quote, close-quote, no-open-quote, or no-close-quote
+ const StyleContentType mType;
+
+ // Quote depth before this quote, which is always non-negative.
+ int32_t mDepthBefore;
+
+ nsQuoteNode(StyleContentType aType, uint32_t aContentIndex)
+ : nsGenConNode(aContentIndex), mType(aType), mDepthBefore(0) {
+ NS_ASSERTION(aType == StyleContentType::OpenQuote ||
+ aType == StyleContentType::CloseQuote ||
+ aType == StyleContentType::NoOpenQuote ||
+ aType == StyleContentType::NoCloseQuote,
+ "incorrect type");
+ NS_ASSERTION(aContentIndex <= INT32_MAX, "out of range");
+ }
+
+ virtual bool InitTextFrame(nsGenConList* aList, nsIFrame* aPseudoFrame,
+ nsIFrame* aTextFrame) override;
+
+ // is this 'open-quote' or 'no-open-quote'?
+ bool IsOpenQuote() {
+ return mType == StyleContentType::OpenQuote ||
+ mType == StyleContentType::NoOpenQuote;
+ }
+
+ // is this 'close-quote' or 'no-close-quote'?
+ bool IsCloseQuote() { return !IsOpenQuote(); }
+
+ // is this 'open-quote' or 'close-quote'?
+ bool IsRealQuote() {
+ return mType == StyleContentType::OpenQuote ||
+ mType == StyleContentType::CloseQuote;
+ }
+
+ // Depth of the quote for *this* node. Either non-negative or -1.
+ // -1 means this is a closing quote that tried to decrement the
+ // counter below zero (which means no quote should be rendered).
+ int32_t Depth() { return IsOpenQuote() ? mDepthBefore : mDepthBefore - 1; }
+
+ // always non-negative
+ int32_t DepthAfter() {
+ return IsOpenQuote() ? mDepthBefore + 1
+ : (mDepthBefore == 0 ? 0 : mDepthBefore - 1);
+ }
+
+ // The text that should be displayed for this quote.
+ nsString Text();
+};
+
+class nsQuoteList : public nsGenConList {
+ private:
+ nsQuoteNode* FirstNode() {
+ return static_cast<nsQuoteNode*>(mList.getFirst());
+ }
+
+ public:
+ explicit nsQuoteList(mozilla::ContainStyleScope* aScope) : mScope(aScope) {}
+
+ // assign the correct |mDepthBefore| value to a node that has been inserted
+ // Should be called immediately after calling |Insert|.
+ void Calc(nsQuoteNode* aNode);
+
+ nsQuoteNode* Next(nsQuoteNode* aNode) {
+ return static_cast<nsQuoteNode*>(nsGenConList::Next(aNode));
+ }
+ nsQuoteNode* Prev(nsQuoteNode* aNode) {
+ return static_cast<nsQuoteNode*>(nsGenConList::Prev(aNode));
+ }
+
+ void RecalcAll();
+#ifdef DEBUG
+ void PrintChain();
+#endif
+
+ private:
+ mozilla::ContainStyleScope* mScope;
+};
+
+#endif /* nsQuoteList_h___ */
diff --git a/layout/base/nsRefreshDriver.cpp b/layout/base/nsRefreshDriver.cpp
new file mode 100644
index 0000000000..af780bb192
--- /dev/null
+++ b/layout/base/nsRefreshDriver.cpp
@@ -0,0 +1,3323 @@
+/* -*- 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 to notify things that animate before a refresh, at an appropriate
+ * refresh rate. (Perhaps temporary, until replaced by compositor.)
+ *
+ * Chrome and each tab have their own RefreshDriver, which in turn
+ * hooks into one of a few global timer based on RefreshDriverTimer,
+ * defined below. There are two main global timers -- one for active
+ * animations, and one for inactive ones. These are implemented as
+ * subclasses of RefreshDriverTimer; see below for a description of
+ * their implementations. In the future, additional timer types may
+ * implement things like blocking on vsync.
+ */
+
+#include "nsRefreshDriver.h"
+#include "mozilla/DataMutex.h"
+#include "nsThreadUtils.h"
+
+#ifdef XP_WIN
+# include <windows.h>
+// mmsystem isn't part of WIN32_LEAN_AND_MEAN, so we have
+// to manually include it
+# include <mmsystem.h>
+# include "WinUtils.h"
+#endif
+
+#include "mozilla/AnimationEventDispatcher.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/BasePrincipal.h"
+#include "mozilla/dom/MediaQueryList.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/DisplayPortUtils.h"
+#include "mozilla/Hal.h"
+#include "mozilla/InputTaskManager.h"
+#include "mozilla/IntegerRange.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/VsyncTaskManager.h"
+#include "nsITimer.h"
+#include "nsLayoutUtils.h"
+#include "nsPresContext.h"
+#include "imgRequest.h"
+#include "nsComponentManagerUtils.h"
+#include "mozilla/Logging.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "nsIXULRuntime.h"
+#include "jsapi.h"
+#include "nsContentUtils.h"
+#include "nsTextFrame.h"
+#include "mozilla/PendingFullscreenEvent.h"
+#include "mozilla/dom/PerformanceMainThread.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_gfx.h"
+#include "mozilla/StaticPrefs_idle_period.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_page_load.h"
+#include "nsViewManager.h"
+#include "GeckoProfiler.h"
+#include "mozilla/dom/BrowserChild.h"
+#include "mozilla/dom/CallbackDebuggerNotification.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/Event.h"
+#include "mozilla/dom/Performance.h"
+#include "mozilla/dom/Selection.h"
+#include "mozilla/dom/VsyncMainChild.h"
+#include "mozilla/dom/WindowBinding.h"
+#include "mozilla/dom/LargestContentfulPaint.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/RestyleManager.h"
+#include "mozilla/TaskController.h"
+#include "imgIContainer.h"
+#include "mozilla/dom/ScriptSettings.h"
+#include "nsDocShell.h"
+#include "nsISimpleEnumerator.h"
+#include "nsJSEnvironment.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Telemetry.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "VsyncSource.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "mozilla/Unused.h"
+#include "nsAnimationManager.h"
+#include "nsDisplayList.h"
+#include "nsDOMNavigationTiming.h"
+#include "nsTransitionManager.h"
+
+#if defined(MOZ_WIDGET_ANDROID)
+# include "VRManagerChild.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+#include "nsXULPopupManager.h"
+
+#include <numeric>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::ipc;
+using namespace mozilla::dom;
+using namespace mozilla::layout;
+
+static mozilla::LazyLogModule sRefreshDriverLog("nsRefreshDriver");
+#define LOG(...) \
+ MOZ_LOG(sRefreshDriverLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+
+// after 10 minutes, stop firing off inactive timers
+#define DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS 600
+
+// The number of seconds spent skipping frames because we are waiting for the
+// compositor before logging.
+#if defined(MOZ_ASAN)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && !defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING 5
+#elif defined(DEBUG) && defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 20 : 5)
+#elif defined(MOZ_VALGRIND)
+# define REFRESH_WAIT_WARNING (RUNNING_ON_VALGRIND ? 10 : 1)
+#else
+# define REFRESH_WAIT_WARNING 1
+#endif
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsRefreshDriver::TickReasons);
+
+namespace {
+// The number outstanding nsRefreshDrivers (that have been created but not
+// disconnected). When this reaches zero we will call
+// nsRefreshDriver::Shutdown.
+static uint32_t sRefreshDriverCount = 0;
+} // namespace
+
+namespace mozilla {
+
+static TimeStamp sMostRecentHighRateVsync;
+
+static TimeDuration sMostRecentHighRate;
+
+/*
+ * The base class for all global refresh driver timers. It takes care
+ * of managing the list of refresh drivers attached to them and
+ * provides interfaces for querying/setting the rate and actually
+ * running a timer 'Tick'. Subclasses must implement StartTimer(),
+ * StopTimer(), and ScheduleNextTick() -- the first two just
+ * start/stop whatever timer mechanism is in use, and ScheduleNextTick
+ * is called at the start of the Tick() implementation to set a time
+ * for the next tick.
+ */
+class RefreshDriverTimer {
+ public:
+ RefreshDriverTimer() = default;
+
+ NS_INLINE_DECL_REFCOUNTING(RefreshDriverTimer)
+
+ virtual void AddRefreshDriver(nsRefreshDriver* aDriver) {
+ LOG("[%p] AddRefreshDriver %p", this, aDriver);
+
+ bool startTimer =
+ mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(!mRootRefreshDrivers.Contains(aDriver),
+ "Adding a duplicate root refresh driver!");
+ mRootRefreshDrivers.AppendElement(aDriver);
+ } else {
+ NS_ASSERTION(!mContentRefreshDrivers.Contains(aDriver),
+ "Adding a duplicate content refresh driver!");
+ mContentRefreshDrivers.AppendElement(aDriver);
+ }
+
+ if (startTimer) {
+ StartTimer();
+ }
+ }
+
+ void RemoveRefreshDriver(nsRefreshDriver* aDriver) {
+ LOG("[%p] RemoveRefreshDriver %p", this, aDriver);
+
+ if (IsRootRefreshDriver(aDriver)) {
+ NS_ASSERTION(mRootRefreshDrivers.Contains(aDriver),
+ "RemoveRefreshDriver for a refresh driver that's not in the "
+ "root refresh list!");
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ // During PresContext shutdown, we can't accurately detect
+ // if a root refresh driver exists or not. Therefore, we have to
+ // search and find out which list this driver exists in.
+ if (!rootContext) {
+ if (mRootRefreshDrivers.Contains(aDriver)) {
+ mRootRefreshDrivers.RemoveElement(aDriver);
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
+ "RemoveRefreshDriver without a display root for a "
+ "driver that is not in the content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ } else {
+ NS_ASSERTION(mContentRefreshDrivers.Contains(aDriver),
+ "RemoveRefreshDriver for a driver that is not in the "
+ "content refresh list");
+ mContentRefreshDrivers.RemoveElement(aDriver);
+ }
+ }
+
+ bool stopTimer =
+ mContentRefreshDrivers.IsEmpty() && mRootRefreshDrivers.IsEmpty();
+ if (stopTimer) {
+ StopTimer();
+ }
+ }
+
+ TimeStamp MostRecentRefresh() const { return mLastFireTime; }
+ VsyncId MostRecentRefreshVsyncId() const { return mLastFireId; }
+ virtual bool IsBlocked() { return false; }
+
+ virtual TimeDuration GetTimerRate() = 0;
+
+ TimeStamp GetIdleDeadlineHint(TimeStamp aDefault) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!IsTicking() && !gfxPlatform::IsInLayoutAsapMode()) {
+ return aDefault;
+ }
+
+ TimeStamp mostRecentRefresh = MostRecentRefresh();
+ TimeDuration refreshPeriod = GetTimerRate();
+ TimeStamp idleEnd = mostRecentRefresh + refreshPeriod;
+ double highRateMultiplier = nsRefreshDriver::HighRateMultiplier();
+
+ // If we haven't painted for some time, then guess that we won't paint
+ // again for a while, so the refresh driver is not a good way to predict
+ // idle time.
+ if (highRateMultiplier == 1.0 &&
+ (idleEnd +
+ refreshPeriod *
+ StaticPrefs::layout_idle_period_required_quiescent_frames() <
+ TimeStamp::Now())) {
+ return aDefault;
+ }
+
+ // End the predicted idle time a little early, the amount controlled by a
+ // pref, to prevent overrunning the idle time and delaying a frame.
+ // But do that only if we aren't in high rate mode.
+ idleEnd = idleEnd - TimeDuration::FromMilliseconds(
+ highRateMultiplier *
+ StaticPrefs::layout_idle_period_time_limit());
+ return idleEnd < aDefault ? idleEnd : aDefault;
+ }
+
+ Maybe<TimeStamp> GetNextTickHint() {
+ MOZ_ASSERT(NS_IsMainThread());
+ TimeStamp nextTick = MostRecentRefresh() + GetTimerRate();
+ return nextTick < TimeStamp::Now() ? Nothing() : Some(nextTick);
+ }
+
+ // Returns null if the RefreshDriverTimer is attached to several
+ // RefreshDrivers. That may happen for example when there are
+ // several windows open.
+ nsPresContext* GetPresContextForOnlyRefreshDriver() {
+ if (mRootRefreshDrivers.Length() == 1 && mContentRefreshDrivers.IsEmpty()) {
+ return mRootRefreshDrivers[0]->GetPresContext();
+ }
+ if (mContentRefreshDrivers.Length() == 1 && mRootRefreshDrivers.IsEmpty()) {
+ return mContentRefreshDrivers[0]->GetPresContext();
+ }
+ return nullptr;
+ }
+
+ bool IsAnyToplevelContentPageLoading() {
+ for (nsTArray<RefPtr<nsRefreshDriver>>* drivers :
+ {&mRootRefreshDrivers, &mContentRefreshDrivers}) {
+ for (RefPtr<nsRefreshDriver>& driver : *drivers) {
+ if (nsPresContext* pc = driver->GetPresContext()) {
+ if (pc->Document()->IsTopLevelContentDocument() &&
+ pc->Document()->GetReadyStateEnum() <
+ Document::READYSTATE_COMPLETE) {
+ return true;
+ }
+ }
+ }
+ }
+
+ return false;
+ }
+
+ protected:
+ virtual ~RefreshDriverTimer() {
+ MOZ_ASSERT(
+ mContentRefreshDrivers.Length() == 0,
+ "Should have removed all content refresh drivers from here by now!");
+ MOZ_ASSERT(
+ mRootRefreshDrivers.Length() == 0,
+ "Should have removed all root refresh drivers from here by now!");
+ }
+
+ virtual void StartTimer() = 0;
+ virtual void StopTimer() = 0;
+ virtual void ScheduleNextTick(TimeStamp aNowTime) = 0;
+
+ public:
+ virtual bool IsTicking() const = 0;
+
+ protected:
+ bool IsRootRefreshDriver(nsRefreshDriver* aDriver) {
+ nsPresContext* pc = aDriver->GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (!rootContext) {
+ return false;
+ }
+
+ return aDriver == rootContext->RefreshDriver();
+ }
+
+ /*
+ * Actually runs a tick, poking all the attached RefreshDrivers.
+ * Grabs the "now" time via TimeStamp::Now().
+ */
+ void Tick() {
+ TimeStamp now = TimeStamp::Now();
+ Tick(VsyncId(), now);
+ }
+
+ void TickRefreshDrivers(VsyncId aId, TimeStamp aNow,
+ nsTArray<RefPtr<nsRefreshDriver>>& aDrivers) {
+ if (aDrivers.IsEmpty()) {
+ return;
+ }
+
+ for (nsRefreshDriver* driver : aDrivers.Clone()) {
+ // don't poke this driver if it's in test mode
+ if (driver->IsTestControllingRefreshesEnabled()) {
+ continue;
+ }
+
+ TickDriver(driver, aId, aNow);
+ }
+ }
+
+ /*
+ * Tick the refresh drivers based on the given timestamp.
+ */
+ void Tick(VsyncId aId, TimeStamp now) {
+ ScheduleNextTick(now);
+
+ mLastFireTime = now;
+ mLastFireId = aId;
+
+ LOG("[%p] ticking drivers...", this);
+
+ TickRefreshDrivers(aId, now, mContentRefreshDrivers);
+ TickRefreshDrivers(aId, now, mRootRefreshDrivers);
+
+ LOG("[%p] done.", this);
+ }
+
+ static void TickDriver(nsRefreshDriver* driver, VsyncId aId, TimeStamp now) {
+ driver->Tick(aId, now);
+ }
+
+ TimeStamp mLastFireTime;
+ VsyncId mLastFireId;
+ TimeStamp mTargetTime;
+
+ nsTArray<RefPtr<nsRefreshDriver>> mContentRefreshDrivers;
+ nsTArray<RefPtr<nsRefreshDriver>> mRootRefreshDrivers;
+
+ // useful callback for nsITimer-based derived classes, here
+ // because of c++ protected shenanigans
+ static void TimerTick(nsITimer* aTimer, void* aClosure) {
+ RefPtr<RefreshDriverTimer> timer =
+ static_cast<RefreshDriverTimer*>(aClosure);
+ timer->Tick();
+ }
+};
+
+/*
+ * A RefreshDriverTimer that uses a nsITimer as the underlying timer. Note that
+ * this is a ONE_SHOT timer, not a repeating one! Subclasses are expected to
+ * implement ScheduleNextTick and intelligently calculate the next time to tick,
+ * and to reset mTimer. Using a repeating nsITimer gets us into a lot of pain
+ * with its attempt at intelligent slack removal and such, so we don't do it.
+ */
+class SimpleTimerBasedRefreshDriverTimer : public RefreshDriverTimer {
+ public:
+ /*
+ * aRate -- the delay, in milliseconds, requested between timer firings
+ */
+ explicit SimpleTimerBasedRefreshDriverTimer(double aRate) {
+ SetRate(aRate);
+ mTimer = NS_NewTimer();
+ }
+
+ virtual ~SimpleTimerBasedRefreshDriverTimer() override { StopTimer(); }
+
+ // will take effect at next timer tick
+ virtual void SetRate(double aNewRate) {
+ mRateMilliseconds = aNewRate;
+ mRateDuration = TimeDuration::FromMilliseconds(mRateMilliseconds);
+ }
+
+ double GetRate() const { return mRateMilliseconds; }
+
+ TimeDuration GetTimerRate() override { return mRateDuration; }
+
+ protected:
+ void StartTimer() override {
+ // pretend we just fired, and we schedule the next tick normally
+ mLastFireTime = TimeStamp::Now();
+ mLastFireId = VsyncId();
+
+ mTargetTime = mLastFireTime + mRateDuration;
+
+ uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
+ mTimer->InitWithNamedFuncCallback(
+ TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
+ "SimpleTimerBasedRefreshDriverTimer::StartTimer");
+ }
+
+ void StopTimer() override { mTimer->Cancel(); }
+
+ double mRateMilliseconds;
+ TimeDuration mRateDuration;
+ RefPtr<nsITimer> mTimer;
+};
+
+/*
+ * A refresh driver that listens to vsync events and ticks the refresh driver
+ * on vsync intervals. We throttle the refresh driver if we get too many
+ * vsync events and wait to catch up again.
+ */
+class VsyncRefreshDriverTimer : public RefreshDriverTimer {
+ public:
+ // This is used in the parent process for all platforms except Linux Wayland.
+ static RefPtr<VsyncRefreshDriverTimer>
+ CreateForParentProcessWithGlobalVsync() {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ RefPtr<VsyncDispatcher> vsyncDispatcher =
+ gfxPlatform::GetPlatform()->GetGlobalVsyncDispatcher();
+ RefPtr<VsyncRefreshDriverTimer> timer =
+ new VsyncRefreshDriverTimer(std::move(vsyncDispatcher), nullptr);
+ return timer.forget();
+ }
+
+ // This is used in the parent process for Linux Wayland only, where we have a
+ // per-widget VsyncSource which is independent from the gfxPlatform's global
+ // VsyncSource.
+ static RefPtr<VsyncRefreshDriverTimer>
+ CreateForParentProcessWithLocalVsyncDispatcher(
+ RefPtr<VsyncDispatcher>&& aVsyncDispatcher) {
+ MOZ_RELEASE_ASSERT(XRE_IsParentProcess());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ RefPtr<VsyncRefreshDriverTimer> timer =
+ new VsyncRefreshDriverTimer(std::move(aVsyncDispatcher), nullptr);
+ return timer.forget();
+ }
+
+ // This is used in the content process.
+ static RefPtr<VsyncRefreshDriverTimer> CreateForContentProcess(
+ RefPtr<VsyncMainChild>&& aVsyncChild) {
+ MOZ_RELEASE_ASSERT(XRE_IsContentProcess());
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ RefPtr<VsyncRefreshDriverTimer> timer =
+ new VsyncRefreshDriverTimer(nullptr, std::move(aVsyncChild));
+ return timer.forget();
+ }
+
+ TimeDuration GetTimerRate() override {
+ if (mVsyncDispatcher) {
+ mVsyncRate = mVsyncDispatcher->GetVsyncRate();
+ } else if (mVsyncChild) {
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+
+ // If hardware queries fail / are unsupported, we have to just guess.
+ return mVsyncRate != TimeDuration::Forever()
+ ? mVsyncRate
+ : TimeDuration::FromMilliseconds(1000.0 / 60.0);
+ }
+
+ bool IsBlocked() override {
+ return !mSuspendVsyncPriorityTicksUntil.IsNull() &&
+ mSuspendVsyncPriorityTicksUntil > TimeStamp::Now() &&
+ ShouldGiveNonVsyncTasksMoreTime();
+ }
+
+ private:
+ // RefreshDriverVsyncObserver redirects vsync notifications to the main thread
+ // and calls VsyncRefreshDriverTimer::NotifyVsyncOnMainThread on it. It also
+ // acts as a weak reference to the refresh driver timer, dropping its
+ // reference when RefreshDriverVsyncObserver::Shutdown is called from the
+ // timer's destructor.
+ //
+ // RefreshDriverVsyncObserver::NotifyVsync is called from different places
+ // depending on the process type.
+ //
+ // Parent process:
+ // NotifyVsync is called by RefreshDriverVsyncDispatcher, on a background
+ // thread. RefreshDriverVsyncDispatcher keeps strong references to its
+ // VsyncObservers, both in its array of observers and while calling
+ // NotifyVsync. So it might drop its last reference to the observer on a
+ // background thread. This means that the VsyncRefreshDriverTimer itself can't
+ // be the observer (because its destructor would potentially be run on a
+ // background thread), and it's why we use this separate class.
+ //
+ // Child process:
+ // NotifyVsync is called by VsyncMainChild, on the main thread.
+ // VsyncMainChild keeps raw pointers to its observers.
+ class RefreshDriverVsyncObserver final : public VsyncObserver {
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
+ VsyncRefreshDriverTimer::RefreshDriverVsyncObserver, override)
+
+ public:
+ explicit RefreshDriverVsyncObserver(
+ VsyncRefreshDriverTimer* aVsyncRefreshDriverTimer)
+ : mVsyncRefreshDriverTimer(aVsyncRefreshDriverTimer),
+ mLastPendingVsyncNotification(
+ "RefreshDriverVsyncObserver::mLastPendingVsyncNotification") {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ void NotifyVsync(const VsyncEvent& aVsync) override {
+ // Compress vsync notifications such that only 1 may run at a time
+ // This is so that we don't flood the refresh driver with vsync messages
+ // if the main thread is blocked for long periods of time
+ { // scope lock
+ auto pendingVsync = mLastPendingVsyncNotification.Lock();
+ bool hadPendingVsync = pendingVsync->isSome();
+ *pendingVsync = Some(aVsync);
+ if (hadPendingVsync) {
+ return;
+ }
+ }
+
+ if (XRE_IsContentProcess()) {
+ // In the content process, NotifyVsync is called by VsyncMainChild on
+ // the main thread. No need to use a runnable, just call
+ // NotifyVsyncTimerOnMainThread() directly.
+ NotifyVsyncTimerOnMainThread();
+ return;
+ }
+
+ // In the parent process, NotifyVsync is called on the vsync thread, which
+ // on most platforms is different from the main thread, so we need to
+ // dispatch a runnable for running NotifyVsyncTimerOnMainThread on the
+ // main thread.
+ // TODO: On Linux Wayland, the vsync thread is currently the main thread,
+ // and yet we still dispatch the runnable. Do we need to?
+ bool useVsyncPriority = mozilla::BrowserTabsRemoteAutostart();
+ nsCOMPtr<nsIRunnable> vsyncEvent = new PrioritizableRunnable(
+ NS_NewRunnableFunction(
+ "RefreshDriverVsyncObserver::NotifyVsyncTimerOnMainThread",
+ [self = RefPtr{this}]() {
+ self->NotifyVsyncTimerOnMainThread();
+ }),
+ useVsyncPriority ? nsIRunnablePriority::PRIORITY_VSYNC
+ : nsIRunnablePriority::PRIORITY_NORMAL);
+ NS_DispatchToMainThread(vsyncEvent);
+ }
+
+ void NotifyVsyncTimerOnMainThread() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (!mVsyncRefreshDriverTimer) {
+ // Ignore calls after Shutdown.
+ return;
+ }
+
+ VsyncEvent vsyncEvent;
+ {
+ // Get the last of the queued-up vsync notifications.
+ auto pendingVsync = mLastPendingVsyncNotification.Lock();
+ MOZ_RELEASE_ASSERT(
+ pendingVsync->isSome(),
+ "We should always have a pending vsync notification here.");
+ vsyncEvent = pendingVsync->extract();
+ }
+
+ // Call VsyncRefreshDriverTimer::NotifyVsyncOnMainThread, and keep a
+ // strong reference to it while calling the method.
+ RefPtr<VsyncRefreshDriverTimer> timer = mVsyncRefreshDriverTimer;
+ timer->NotifyVsyncOnMainThread(vsyncEvent);
+ }
+
+ void Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ mVsyncRefreshDriverTimer = nullptr;
+ }
+
+ private:
+ ~RefreshDriverVsyncObserver() = default;
+
+ // VsyncRefreshDriverTimer holds this RefreshDriverVsyncObserver and it will
+ // be always available before Shutdown(). We can just use the raw pointer
+ // here.
+ // Only accessed on the main thread.
+ VsyncRefreshDriverTimer* mVsyncRefreshDriverTimer;
+
+ // Non-empty between a call to NotifyVsync and a call to
+ // NotifyVsyncOnMainThread. When multiple vsync notifications have been
+ // received between those two calls, this contains the last of the pending
+ // notifications. This is used both in the parent process and in the child
+ // process, but it only does something useful in the parent process. In the
+ // child process, both calls happen on the main thread right after one
+ // another, so there's only one notification to keep track of; vsync
+ // notification coalescing for child processes happens at the IPC level
+ // instead.
+ DataMutex<Maybe<VsyncEvent>> mLastPendingVsyncNotification;
+
+ }; // RefreshDriverVsyncObserver
+
+ VsyncRefreshDriverTimer(RefPtr<VsyncDispatcher>&& aVsyncDispatcher,
+ RefPtr<VsyncMainChild>&& aVsyncChild)
+ : mVsyncDispatcher(aVsyncDispatcher),
+ mVsyncChild(aVsyncChild),
+ mVsyncRate(TimeDuration::Forever()),
+ mRecentVsync(TimeStamp::Now()),
+ mLastTickStart(TimeStamp::Now()),
+ mLastIdleTaskCount(0),
+ mLastRunOutOfMTTasksCount(0),
+ mProcessedVsync(true),
+ mHasPendingLowPrioTask(false) {
+ mVsyncObserver = new RefreshDriverVsyncObserver(this);
+ }
+
+ ~VsyncRefreshDriverTimer() override {
+ if (mVsyncDispatcher) {
+ mVsyncDispatcher->RemoveVsyncObserver(mVsyncObserver);
+ mVsyncDispatcher = nullptr;
+ } else if (mVsyncChild) {
+ mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
+ mVsyncChild = nullptr;
+ }
+
+ // Detach current vsync timer from this VsyncObserver. The observer will no
+ // longer tick this timer.
+ mVsyncObserver->Shutdown();
+ mVsyncObserver = nullptr;
+ }
+
+ bool ShouldGiveNonVsyncTasksMoreTime(bool aCheckOnlyNewPendingTasks = false) {
+ TaskController* taskController = TaskController::Get();
+ IdleTaskManager* idleTaskManager = taskController->GetIdleTaskManager();
+ VsyncTaskManager* vsyncTaskManager = VsyncTaskManager::Get();
+
+ // Note, pendingTaskCount includes also all the pending idle and vsync
+ // tasks.
+ uint64_t pendingTaskCount =
+ taskController->PendingMainthreadTaskCountIncludingSuspended();
+ uint64_t pendingIdleTaskCount = idleTaskManager->PendingTaskCount();
+ uint64_t pendingVsyncTaskCount = vsyncTaskManager->PendingTaskCount();
+ if (!(pendingTaskCount > (pendingIdleTaskCount + pendingVsyncTaskCount))) {
+ return false;
+ }
+ if (aCheckOnlyNewPendingTasks) {
+ return true;
+ }
+
+ uint64_t idleTaskCount = idleTaskManager->ProcessedTaskCount();
+
+ // If we haven't processed new idle tasks and we have pending
+ // non-idle tasks, give those non-idle tasks more time,
+ // but only if the main thread wasn't totally empty at some point.
+ // In the parent process RunOutOfMTTasksCount() is less meaningful
+ // because some of the tasks run through AppShell.
+ return mLastIdleTaskCount == idleTaskCount &&
+ (taskController->RunOutOfMTTasksCount() ==
+ mLastRunOutOfMTTasksCount ||
+ XRE_IsParentProcess());
+ }
+
+ void NotifyVsyncOnMainThread(const VsyncEvent& aVsyncEvent) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mRecentVsync = aVsyncEvent.mTime;
+ mRecentVsyncId = aVsyncEvent.mId;
+ if (!mSuspendVsyncPriorityTicksUntil.IsNull() &&
+ mSuspendVsyncPriorityTicksUntil > TimeStamp::Now()) {
+ if (ShouldGiveNonVsyncTasksMoreTime()) {
+ if (!IsAnyToplevelContentPageLoading()) {
+ // If pages aren't loading and there aren't other tasks to run,
+ // trigger the pending vsync notification.
+ mPendingVsync = mRecentVsync;
+ mPendingVsyncId = mRecentVsyncId;
+ if (!mHasPendingLowPrioTask) {
+ mHasPendingLowPrioTask = true;
+ NS_DispatchToMainThreadQueue(
+ NS_NewRunnableFunction(
+ "NotifyVsyncOnMainThread[low priority]",
+ [self = RefPtr{this}]() {
+ self->mHasPendingLowPrioTask = false;
+ if (self->mRecentVsync == self->mPendingVsync &&
+ self->mRecentVsyncId == self->mPendingVsyncId &&
+ !self->ShouldGiveNonVsyncTasksMoreTime()) {
+ self->mSuspendVsyncPriorityTicksUntil = TimeStamp();
+ self->NotifyVsyncOnMainThread({self->mPendingVsyncId,
+ self->mPendingVsync,
+ /* unused */
+ TimeStamp()});
+ }
+ }),
+ EventQueuePriority::Low);
+ }
+ }
+ return;
+ }
+
+ // Clear the value since we aren't blocking anymore because there aren't
+ // any non-idle tasks to process.
+ mSuspendVsyncPriorityTicksUntil = TimeStamp();
+ }
+
+ if (StaticPrefs::layout_lower_priority_refresh_driver_during_load() &&
+ ShouldGiveNonVsyncTasksMoreTime()) {
+ nsPresContext* pctx = GetPresContextForOnlyRefreshDriver();
+ if (pctx && pctx->HadFirstContentfulPaint() && pctx->Document() &&
+ pctx->Document()->GetReadyStateEnum() <
+ Document::READYSTATE_COMPLETE) {
+ nsPIDOMWindowInner* win = pctx->Document()->GetInnerWindow();
+ uint32_t frameRateMultiplier = pctx->GetNextFrameRateMultiplier();
+ if (!frameRateMultiplier) {
+ pctx->DidUseFrameRateMultiplier();
+ }
+ if (win && frameRateMultiplier) {
+ dom::Performance* perf = win->GetPerformance();
+ // Limit slower refresh rate to 5 seconds between the
+ // first contentful paint and page load.
+ if (perf &&
+ perf->Now() < StaticPrefs::page_load_deprioritization_period()) {
+ if (mProcessedVsync) {
+ mProcessedVsync = false;
+ TimeDuration rate = GetTimerRate();
+ uint32_t slowRate = static_cast<uint32_t>(rate.ToMilliseconds() *
+ frameRateMultiplier);
+ pctx->DidUseFrameRateMultiplier();
+ nsCOMPtr<nsIRunnable> vsyncEvent = NewRunnableMethod<>(
+ "VsyncRefreshDriverTimer::IdlePriorityNotify", this,
+ &VsyncRefreshDriverTimer::IdlePriorityNotify);
+ NS_DispatchToCurrentThreadQueue(vsyncEvent.forget(), slowRate,
+ EventQueuePriority::Idle);
+ }
+ return;
+ }
+ }
+ }
+ }
+
+ TickRefreshDriver(aVsyncEvent.mId, aVsyncEvent.mTime);
+ }
+
+ void RecordTelemetryProbes(TimeStamp aVsyncTimestamp) {
+ MOZ_ASSERT(NS_IsMainThread());
+#ifndef ANDROID /* bug 1142079 */
+ if (XRE_IsParentProcess()) {
+ TimeDuration vsyncLatency = TimeStamp::Now() - aVsyncTimestamp;
+ uint32_t sample = (uint32_t)vsyncLatency.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CHROME_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
+ } else if (mVsyncRate != TimeDuration::Forever()) {
+ TimeDuration contentDelay =
+ (TimeStamp::Now() - mLastTickStart) - mVsyncRate;
+ if (contentDelay.ToMilliseconds() < 0) {
+ // Vsyncs are noisy and some can come at a rate quicker than
+ // the reported hardware rate. In those cases, consider that we have 0
+ // delay.
+ contentDelay = TimeDuration::FromMilliseconds(0);
+ }
+ uint32_t sample = (uint32_t)contentDelay.ToMilliseconds();
+ Telemetry::Accumulate(Telemetry::FX_REFRESH_DRIVER_CONTENT_FRAME_DELAY_MS,
+ sample);
+ Telemetry::Accumulate(
+ Telemetry::FX_REFRESH_DRIVER_SYNC_SCROLL_FRAME_DELAY_MS, sample);
+ } else {
+ // Request the vsync rate which VsyncChild stored the last time it got a
+ // vsync notification.
+ mVsyncRate = mVsyncChild->GetVsyncRate();
+ }
+#endif
+ }
+
+ void OnTimerStart() {
+ mLastTickStart = TimeStamp::Now();
+ mLastTickEnd = TimeStamp();
+ mLastIdleTaskCount = 0;
+ }
+
+ void IdlePriorityNotify() {
+ if (mLastProcessedTick.IsNull() || mRecentVsync > mLastProcessedTick) {
+ // mSuspendVsyncPriorityTicksUntil is for high priority vsync
+ // notifications only.
+ mSuspendVsyncPriorityTicksUntil = TimeStamp();
+ TickRefreshDriver(mRecentVsyncId, mRecentVsync);
+ }
+
+ mProcessedVsync = true;
+ }
+
+ hal::PerformanceHintSession* GetPerformanceHintSession() {
+ // The ContentChild creates/destroys the PerformanceHintSession in response
+ // to the process' priority being foregrounded/backgrounded. We can only use
+ // this session when using a single vsync source for the process, otherwise
+ // these threads may be performing work for multiple
+ // VsyncRefreshDriverTimers and we will misreport the work duration.
+ const ContentChild* contentChild = ContentChild::GetSingleton();
+ if (contentChild && mVsyncChild) {
+ return contentChild->PerformanceHintSession();
+ }
+
+ return nullptr;
+ }
+
+ void TickRefreshDriver(VsyncId aId, TimeStamp aVsyncTimestamp) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ RecordTelemetryProbes(aVsyncTimestamp);
+
+ TimeStamp tickStart = TimeStamp::Now();
+
+ const TimeDuration previousRate = mVsyncRate;
+ const TimeDuration rate = GetTimerRate();
+
+ hal::PerformanceHintSession* const performanceHintSession =
+ GetPerformanceHintSession();
+ if (performanceHintSession && rate != previousRate) {
+ performanceHintSession->UpdateTargetWorkDuration(
+ ContentChild::GetPerformanceHintTarget(rate));
+ }
+
+ if (TimeDuration::FromMilliseconds(nsRefreshDriver::DefaultInterval()) >
+ rate) {
+ sMostRecentHighRateVsync = tickStart;
+ sMostRecentHighRate = rate;
+ }
+
+ // On 32-bit Windows we sometimes get times where TimeStamp::Now() is not
+ // monotonic because the underlying system apis produce non-monontonic
+ // results. (bug 1306896)
+#if !defined(_WIN32)
+ MOZ_ASSERT(aVsyncTimestamp <= tickStart);
+#endif
+
+ bool shouldGiveNonVSyncTasksMoreTime = ShouldGiveNonVsyncTasksMoreTime();
+
+ // Set these variables before calling RunRefreshDrivers so that they are
+ // visible to any nested ticks.
+ mLastTickStart = tickStart;
+ mLastProcessedTick = aVsyncTimestamp;
+
+ RunRefreshDrivers(aId, aVsyncTimestamp);
+
+ TimeStamp tickEnd = TimeStamp::Now();
+
+ if (performanceHintSession) {
+ performanceHintSession->ReportActualWorkDuration(tickEnd - tickStart);
+ }
+
+ // Re-read mLastTickStart in case there was a nested tick inside this
+ // tick.
+ TimeStamp mostRecentTickStart = mLastTickStart;
+
+ // Let also non-RefreshDriver code to run at least for awhile if we have
+ // a mVsyncRefreshDriverTimer.
+ // Always give a tiny bit, 5% of the vsync interval, time outside the
+ // tick
+ // In case there are both normal tasks and RefreshDrivers are doing
+ // work, mSuspendVsyncPriorityTicksUntil will be set to a timestamp in the
+ // future where the period between the previous tick start
+ // (mostRecentTickStart) and the next tick needs to be at least the amount
+ // of work normal tasks and RefreshDrivers did together (minus short grace
+ // period).
+ TimeDuration gracePeriod = rate / int64_t(20);
+
+ if (shouldGiveNonVSyncTasksMoreTime && !mLastTickEnd.IsNull() &&
+ XRE_IsContentProcess() &&
+ // For RefreshDriver scheduling during page load there is currently
+ // idle priority based setup.
+ // XXX Consider to remove the page load specific code paths.
+ !IsAnyToplevelContentPageLoading()) {
+ // In case normal tasks are doing lots of work, we still want to paint
+ // every now and then, so only at maximum 4 * rate of work is counted
+ // here.
+ // If we're giving extra time for tasks outside a tick, try to
+ // ensure the next vsync after that period is handled, so subtract
+ // a grace period.
+ TimeDuration timeForOutsideTick = clamped(
+ tickStart - mLastTickEnd - gracePeriod, gracePeriod, rate * 4);
+ mSuspendVsyncPriorityTicksUntil = tickEnd + timeForOutsideTick;
+ } else if (ShouldGiveNonVsyncTasksMoreTime(true)) {
+ // We've got some new tasks, give them some extra time.
+ // This handles also the case when mLastTickEnd.IsNull() above and we
+ // should give some more time for non-vsync tasks.
+ mSuspendVsyncPriorityTicksUntil = tickEnd + gracePeriod;
+ } else {
+ mSuspendVsyncPriorityTicksUntil = mostRecentTickStart + gracePeriod;
+ }
+
+ mLastIdleTaskCount =
+ TaskController::Get()->GetIdleTaskManager()->ProcessedTaskCount();
+ mLastRunOutOfMTTasksCount = TaskController::Get()->RunOutOfMTTasksCount();
+ mLastTickEnd = tickEnd;
+ }
+
+ void StartTimer() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mLastFireTime = TimeStamp::Now();
+ mLastFireId = VsyncId();
+
+ if (mVsyncDispatcher) {
+ mVsyncDispatcher->AddVsyncObserver(mVsyncObserver);
+ } else if (mVsyncChild) {
+ mVsyncChild->AddChildRefreshTimer(mVsyncObserver);
+ OnTimerStart();
+ }
+ mIsTicking = true;
+ }
+
+ void StopTimer() override {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mVsyncDispatcher) {
+ mVsyncDispatcher->RemoveVsyncObserver(mVsyncObserver);
+ } else if (mVsyncChild) {
+ mVsyncChild->RemoveChildRefreshTimer(mVsyncObserver);
+ }
+ mIsTicking = false;
+ }
+
+ public:
+ bool IsTicking() const override { return mIsTicking; }
+
+ protected:
+ void ScheduleNextTick(TimeStamp aNowTime) override {
+ // Do nothing since we just wait for the next vsync from
+ // RefreshDriverVsyncObserver.
+ }
+
+ void RunRefreshDrivers(VsyncId aId, TimeStamp aTimeStamp) {
+ Tick(aId, aTimeStamp);
+ for (auto& driver : mContentRefreshDrivers) {
+ driver->FinishedVsyncTick();
+ }
+ for (auto& driver : mRootRefreshDrivers) {
+ driver->FinishedVsyncTick();
+ }
+ }
+
+ // Always non-null. Has a weak pointer to us and notifies us of vsync.
+ RefPtr<RefreshDriverVsyncObserver> mVsyncObserver;
+
+ // Used in the parent process. We register mVsyncObserver with it for the
+ // duration during which we want to receive vsync notifications. We also
+ // use it to query the current vsync rate.
+ RefPtr<VsyncDispatcher> mVsyncDispatcher;
+ // Used it the content process. We register mVsyncObserver with it for the
+ // duration during which we want to receive vsync notifications. The
+ // mVsyncChild will be always available before VsyncChild::ActorDestroy().
+ // After ActorDestroy(), StartTimer() and StopTimer() calls will be non-op.
+ RefPtr<VsyncMainChild> mVsyncChild;
+
+ TimeDuration mVsyncRate;
+ bool mIsTicking = false;
+
+ TimeStamp mRecentVsync;
+ VsyncId mRecentVsyncId;
+ // The local start time when RefreshDrivers' Tick was called last time.
+ TimeStamp mLastTickStart;
+ // The local end time of the last RefreshDrivers' tick.
+ TimeStamp mLastTickEnd;
+ // The number of idle tasks the main thread has processed. It is updated
+ // right after RefreshDrivers' tick.
+ uint64_t mLastIdleTaskCount;
+ // If there were no idle tasks, we need to check if the main event queue
+ // was totally empty at times.
+ uint64_t mLastRunOutOfMTTasksCount;
+ // Note, mLastProcessedTick stores the vsync timestamp, which may be coming
+ // from a different process.
+ TimeStamp mLastProcessedTick;
+ // mSuspendVsyncPriorityTicksUntil is used to block too high refresh rate in
+ // case the main thread has also other non-idle tasks to process.
+ // The timestamp is effectively mLastTickEnd + some duration.
+ TimeStamp mSuspendVsyncPriorityTicksUntil;
+ bool mProcessedVsync;
+
+ TimeStamp mPendingVsync;
+ VsyncId mPendingVsyncId;
+ bool mHasPendingLowPrioTask;
+}; // VsyncRefreshDriverTimer
+
+/**
+ * Since the content process takes some time to setup
+ * the vsync IPC connection, this timer is used
+ * during the intial startup process.
+ * During initial startup, the refresh drivers
+ * are ticked off this timer, and are swapped out once content
+ * vsync IPC connection is established.
+ */
+class StartupRefreshDriverTimer : public SimpleTimerBasedRefreshDriverTimer {
+ public:
+ explicit StartupRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate) {}
+
+ protected:
+ void ScheduleNextTick(TimeStamp aNowTime) override {
+ // Since this is only used for startup, it isn't super critical
+ // that we tick at consistent intervals.
+ TimeStamp newTarget = aNowTime + mRateDuration;
+ uint32_t delay =
+ static_cast<uint32_t>((newTarget - aNowTime).ToMilliseconds());
+ mTimer->InitWithNamedFuncCallback(
+ TimerTick, this, delay, nsITimer::TYPE_ONE_SHOT,
+ "StartupRefreshDriverTimer::ScheduleNextTick");
+ mTargetTime = newTarget;
+ }
+
+ public:
+ bool IsTicking() const override { return true; }
+};
+
+/*
+ * A RefreshDriverTimer for inactive documents. When a new refresh driver is
+ * added, the rate is reset to the base (normally 1s/1fps). Every time
+ * it ticks, a single refresh driver is poked. Once they have all been poked,
+ * the duration between ticks doubles, up to mDisableAfterMilliseconds. At that
+ * point, the timer is quiet and doesn't tick (until something is added to it
+ * again).
+ *
+ * When a timer is removed, there is a possibility of another timer
+ * being skipped for one cycle. We could avoid this by adjusting
+ * mNextDriverIndex in RemoveRefreshDriver, but there's little need to
+ * add that complexity. All we want is for inactive drivers to tick
+ * at some point, but we don't care too much about how often.
+ */
+class InactiveRefreshDriverTimer final
+ : public SimpleTimerBasedRefreshDriverTimer {
+ public:
+ explicit InactiveRefreshDriverTimer(double aRate)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(-1.0),
+ mNextDriverIndex(0) {}
+
+ InactiveRefreshDriverTimer(double aRate, double aDisableAfterMilliseconds)
+ : SimpleTimerBasedRefreshDriverTimer(aRate),
+ mNextTickDuration(aRate),
+ mDisableAfterMilliseconds(aDisableAfterMilliseconds),
+ mNextDriverIndex(0) {}
+
+ void AddRefreshDriver(nsRefreshDriver* aDriver) override {
+ RefreshDriverTimer::AddRefreshDriver(aDriver);
+
+ LOG("[%p] inactive timer got new refresh driver %p, resetting rate", this,
+ aDriver);
+
+ // reset the timer, and start with the newly added one next time.
+ mNextTickDuration = mRateMilliseconds;
+
+ // we don't really have to start with the newly added one, but we may as
+ // well not tick the old ones at the fastest rate any more than we need to.
+ mNextDriverIndex = GetRefreshDriverCount() - 1;
+
+ StopTimer();
+ StartTimer();
+ }
+
+ TimeDuration GetTimerRate() override {
+ return TimeDuration::FromMilliseconds(mNextTickDuration);
+ }
+
+ protected:
+ uint32_t GetRefreshDriverCount() {
+ return mContentRefreshDrivers.Length() + mRootRefreshDrivers.Length();
+ }
+
+ void StartTimer() override {
+ mLastFireTime = TimeStamp::Now();
+ mLastFireId = VsyncId();
+
+ mTargetTime = mLastFireTime + mRateDuration;
+
+ uint32_t delay = static_cast<uint32_t>(mRateMilliseconds);
+ mTimer->InitWithNamedFuncCallback(TimerTickOne, this, delay,
+ nsITimer::TYPE_ONE_SHOT,
+ "InactiveRefreshDriverTimer::StartTimer");
+ mIsTicking = true;
+ }
+
+ void StopTimer() override {
+ mTimer->Cancel();
+ mIsTicking = false;
+ }
+
+ void ScheduleNextTick(TimeStamp aNowTime) override {
+ if (mDisableAfterMilliseconds > 0.0 &&
+ mNextTickDuration > mDisableAfterMilliseconds) {
+ // We hit the time after which we should disable
+ // inactive window refreshes; don't schedule anything
+ // until we get kicked by an AddRefreshDriver call.
+ return;
+ }
+
+ // double the next tick time if we've already gone through all of them once
+ if (mNextDriverIndex >= GetRefreshDriverCount()) {
+ mNextTickDuration *= 2.0;
+ mNextDriverIndex = 0;
+ }
+
+ // this doesn't need to be precise; do a simple schedule
+ uint32_t delay = static_cast<uint32_t>(mNextTickDuration);
+ mTimer->InitWithNamedFuncCallback(
+ TimerTickOne, this, delay, nsITimer::TYPE_ONE_SHOT,
+ "InactiveRefreshDriverTimer::ScheduleNextTick");
+
+ LOG("[%p] inactive timer next tick in %f ms [index %d/%d]", this,
+ mNextTickDuration, mNextDriverIndex, GetRefreshDriverCount());
+ }
+
+ public:
+ bool IsTicking() const override { return mIsTicking; }
+
+ protected:
+ /* Runs just one driver's tick. */
+ void TickOne() {
+ TimeStamp now = TimeStamp::Now();
+
+ ScheduleNextTick(now);
+
+ mLastFireTime = now;
+ mLastFireId = VsyncId();
+
+ nsTArray<RefPtr<nsRefreshDriver>> drivers(mContentRefreshDrivers.Clone());
+ drivers.AppendElements(mRootRefreshDrivers);
+ size_t index = mNextDriverIndex;
+
+ if (index < drivers.Length() &&
+ !drivers[index]->IsTestControllingRefreshesEnabled()) {
+ TickDriver(drivers[index], VsyncId(), now);
+ }
+
+ mNextDriverIndex++;
+ }
+
+ static void TimerTickOne(nsITimer* aTimer, void* aClosure) {
+ RefPtr<InactiveRefreshDriverTimer> timer =
+ static_cast<InactiveRefreshDriverTimer*>(aClosure);
+ timer->TickOne();
+ }
+
+ double mNextTickDuration;
+ double mDisableAfterMilliseconds;
+ uint32_t mNextDriverIndex;
+ bool mIsTicking = false;
+};
+
+} // namespace mozilla
+
+static StaticRefPtr<RefreshDriverTimer> sRegularRateTimer;
+static StaticAutoPtr<nsTArray<RefreshDriverTimer*>> sRegularRateTimerList;
+static StaticRefPtr<InactiveRefreshDriverTimer> sThrottledRateTimer;
+
+void nsRefreshDriver::CreateVsyncRefreshTimer() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (gfxPlatform::IsInLayoutAsapMode()) {
+ return;
+ }
+
+ if (!mOwnTimer) {
+ // If available, we fetch the widget-specific vsync source.
+ nsPresContext* pc = GetPresContext();
+ nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
+ if (widget) {
+ if (RefPtr<VsyncDispatcher> vsyncDispatcher =
+ widget->GetVsyncDispatcher()) {
+ mOwnTimer = VsyncRefreshDriverTimer::
+ CreateForParentProcessWithLocalVsyncDispatcher(
+ std::move(vsyncDispatcher));
+ sRegularRateTimerList->AppendElement(mOwnTimer.get());
+ return;
+ }
+ if (BrowserChild* browserChild = widget->GetOwningBrowserChild()) {
+ if (RefPtr<VsyncMainChild> vsyncChildViaPBrowser =
+ browserChild->GetVsyncChild()) {
+ mOwnTimer = VsyncRefreshDriverTimer::CreateForContentProcess(
+ std::move(vsyncChildViaPBrowser));
+ sRegularRateTimerList->AppendElement(mOwnTimer.get());
+ return;
+ }
+ }
+ }
+ }
+ if (!sRegularRateTimer) {
+ if (XRE_IsParentProcess()) {
+ // Make sure all vsync systems are ready.
+ gfxPlatform::GetPlatform();
+ // In parent process, we can create the VsyncRefreshDriverTimer directly.
+ sRegularRateTimer =
+ VsyncRefreshDriverTimer::CreateForParentProcessWithGlobalVsync();
+ } else {
+ PBackgroundChild* actorChild =
+ BackgroundChild::GetOrCreateForCurrentThread();
+ if (NS_WARN_IF(!actorChild)) {
+ return;
+ }
+
+ auto vsyncChildViaPBackground = MakeRefPtr<dom::VsyncMainChild>();
+ dom::PVsyncChild* actor =
+ actorChild->SendPVsyncConstructor(vsyncChildViaPBackground);
+ if (NS_WARN_IF(!actor)) {
+ return;
+ }
+
+ RefPtr<RefreshDriverTimer> vsyncRefreshDriverTimer =
+ VsyncRefreshDriverTimer::CreateForContentProcess(
+ std::move(vsyncChildViaPBackground));
+
+ sRegularRateTimer = std::move(vsyncRefreshDriverTimer);
+ }
+ }
+}
+
+static uint32_t GetFirstFrameDelay(imgIRequest* req) {
+ nsCOMPtr<imgIContainer> container;
+ if (NS_FAILED(req->GetImage(getter_AddRefs(container))) || !container) {
+ return 0;
+ }
+
+ // If this image isn't animated, there isn't a first frame delay.
+ int32_t delay = container->GetFirstFrameDelay();
+ if (delay < 0) return 0;
+
+ return static_cast<uint32_t>(delay);
+}
+
+/* static */
+void nsRefreshDriver::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ // clean up our timers
+ sRegularRateTimer = nullptr;
+ sRegularRateTimerList = nullptr;
+ sThrottledRateTimer = nullptr;
+}
+
+/* static */
+int32_t nsRefreshDriver::DefaultInterval() {
+ return NSToIntRound(1000.0 / gfxPlatform::GetDefaultFrameRate());
+}
+
+/* static */
+double nsRefreshDriver::HighRateMultiplier() {
+ // We're in high rate mode if we've gotten a fast rate during the last
+ // DefaultInterval().
+ bool inHighRateMode =
+ !gfxPlatform::IsInLayoutAsapMode() &&
+ StaticPrefs::layout_expose_high_rate_mode_from_refreshdriver() &&
+ !sMostRecentHighRateVsync.IsNull() &&
+ (sMostRecentHighRateVsync +
+ TimeDuration::FromMilliseconds(DefaultInterval())) > TimeStamp::Now();
+ if (!inHighRateMode) {
+ // Clear the timestamp so that the next call is faster.
+ sMostRecentHighRateVsync = TimeStamp();
+ sMostRecentHighRate = TimeDuration();
+ return 1.0;
+ }
+
+ return sMostRecentHighRate.ToMilliseconds() / DefaultInterval();
+}
+
+// Compute the interval to use for the refresh driver timer, in milliseconds.
+// outIsDefault indicates that rate was not explicitly set by the user
+// so we might choose other, more appropriate rates (e.g. vsync, etc)
+// layout.frame_rate=0 indicates "ASAP mode".
+// In ASAP mode rendering is iterated as fast as possible (typically for stress
+// testing). A target rate of 10k is used internally instead of special-handling
+// 0. Backends which block on swap/present/etc should try to not block when
+// layout.frame_rate=0 - to comply with "ASAP" as much as possible.
+double nsRefreshDriver::GetRegularTimerInterval() const {
+ int32_t rate = Preferences::GetInt("layout.frame_rate", -1);
+ if (rate < 0) {
+ rate = gfxPlatform::GetDefaultFrameRate();
+ } else if (rate == 0) {
+ rate = 10000;
+ }
+
+ return 1000.0 / rate;
+}
+
+/* static */
+double nsRefreshDriver::GetThrottledTimerInterval() {
+ uint32_t rate = StaticPrefs::layout_throttled_frame_rate();
+ return 1000.0 / rate;
+}
+
+/* static */
+TimeDuration nsRefreshDriver::GetMinRecomputeVisibilityInterval() {
+ return TimeDuration::FromMilliseconds(
+ StaticPrefs::layout_visibility_min_recompute_interval_ms());
+}
+
+RefreshDriverTimer* nsRefreshDriver::ChooseTimer() {
+ if (mThrottled) {
+ if (!sThrottledRateTimer) {
+ sThrottledRateTimer = new InactiveRefreshDriverTimer(
+ GetThrottledTimerInterval(),
+ DEFAULT_INACTIVE_TIMER_DISABLE_SECONDS * 1000.0);
+ }
+ return sThrottledRateTimer;
+ }
+
+ if (!mOwnTimer) {
+ CreateVsyncRefreshTimer();
+ }
+
+ if (mOwnTimer) {
+ return mOwnTimer.get();
+ }
+
+ if (!sRegularRateTimer) {
+ double rate = GetRegularTimerInterval();
+ sRegularRateTimer = new StartupRefreshDriverTimer(rate);
+ }
+
+ return sRegularRateTimer;
+}
+
+static nsDocShell* GetDocShell(nsPresContext* aPresContext) {
+ if (!aPresContext) {
+ return nullptr;
+ }
+ return static_cast<nsDocShell*>(aPresContext->GetDocShell());
+}
+
+nsRefreshDriver::nsRefreshDriver(nsPresContext* aPresContext)
+ : mActiveTimer(nullptr),
+ mOwnTimer(nullptr),
+ mPresContext(aPresContext),
+ mRootRefresh(nullptr),
+ mNextTransactionId{0},
+ mFreezeCount(0),
+ mThrottledFrameRequestInterval(
+ TimeDuration::FromMilliseconds(GetThrottledTimerInterval())),
+ mMinRecomputeVisibilityInterval(GetMinRecomputeVisibilityInterval()),
+ mThrottled(false),
+ mNeedToRecomputeVisibility(false),
+ mTestControllingRefreshes(false),
+ mViewManagerFlushIsPending(false),
+ mHasScheduleFlush(false),
+ mInRefresh(false),
+ mWaitingForTransaction(false),
+ mSkippedPaints(false),
+ mResizeSuppressed(false),
+ mNotifyDOMContentFlushed(false),
+ mNeedToUpdateIntersectionObservations(false),
+ mNeedToUpdateResizeObservers(false),
+ mMightNeedMediaQueryListenerUpdate(false),
+ mNeedToUpdateContentRelevancy(false),
+ mInNormalTick(false),
+ mAttemptedExtraTickSinceLastVsync(false),
+ mHasExceededAfterLoadTickPeriod(false),
+ mHasStartedTimerAtLeastOnce(false) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(mPresContext,
+ "Need a pres context to tell us to call Disconnect() later "
+ "and decrement sRefreshDriverCount.");
+ mMostRecentRefresh = TimeStamp::Now();
+ mNextThrottledFrameRequestTick = mMostRecentRefresh;
+ mNextRecomputeVisibilityTick = mMostRecentRefresh;
+
+ if (!sRegularRateTimerList) {
+ sRegularRateTimerList = new nsTArray<RefreshDriverTimer*>();
+ }
+ ++sRefreshDriverCount;
+}
+
+nsRefreshDriver::~nsRefreshDriver() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(ObserverCount() == mEarlyRunners.Length(),
+ "observers, except pending selection scrolls, "
+ "should have been unregistered");
+ MOZ_ASSERT(!mActiveTimer, "timer should be gone");
+ MOZ_ASSERT(!mPresContext,
+ "Should have called Disconnect() and decremented "
+ "sRefreshDriverCount!");
+
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
+ mRootRefresh = nullptr;
+ }
+ if (mOwnTimer && sRegularRateTimerList) {
+ sRegularRateTimerList->RemoveElement(mOwnTimer.get());
+ }
+}
+
+// Method for testing. See nsIDOMWindowUtils.advanceTimeAndRefresh
+// for description.
+void nsRefreshDriver::AdvanceTimeAndRefresh(int64_t aMilliseconds) {
+ // ensure that we're removed from our driver
+ StopTimer();
+
+ if (!mTestControllingRefreshes) {
+ mMostRecentRefresh = TimeStamp::Now();
+
+ mTestControllingRefreshes = true;
+ if (mWaitingForTransaction) {
+ // Disable any refresh driver throttling when entering test mode
+ mWaitingForTransaction = false;
+ mSkippedPaints = false;
+ }
+ }
+
+ mMostRecentRefresh += TimeDuration::FromMilliseconds((double)aMilliseconds);
+
+ mozilla::dom::AutoNoJSAPI nojsapi;
+ DoTick();
+}
+
+void nsRefreshDriver::RestoreNormalRefresh() {
+ mTestControllingRefreshes = false;
+ EnsureTimerStarted(eAllowTimeToGoBackwards);
+ mPendingTransactions.Clear();
+}
+
+TimeStamp nsRefreshDriver::MostRecentRefresh(bool aEnsureTimerStarted) const {
+ // In case of stylo traversal, we have already activated the refresh driver in
+ // RestyleManager::ProcessPendingRestyles().
+ if (aEnsureTimerStarted && !ServoStyleSet::IsInServoTraversal()) {
+ const_cast<nsRefreshDriver*>(this)->EnsureTimerStarted();
+ }
+
+ return mMostRecentRefresh;
+}
+
+void nsRefreshDriver::AddRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType,
+ const char* aObserverDescription) {
+ ObserverArray& array = ArrayFor(aFlushType);
+ MOZ_ASSERT(!array.Contains(aObserver),
+ "We don't want to redundantly register the same observer");
+ array.AppendElement(
+ ObserverData{aObserver, aObserverDescription, TimeStamp::Now(),
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
+ profiler_capture_backtrace(), aFlushType});
+#ifdef DEBUG
+ MOZ_ASSERT(aObserver->mRegistrationCount >= 0,
+ "Registration count shouldn't be able to go negative");
+ aObserver->mRegistrationCount++;
+#endif
+ EnsureTimerStarted();
+}
+
+bool nsRefreshDriver::RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType) {
+ ObserverArray& array = ArrayFor(aFlushType);
+ auto index = array.IndexOf(aObserver);
+ if (index == ObserverArray::array_type::NoIndex) {
+ return false;
+ }
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ auto& data = array.ElementAt(index);
+ nsPrintfCString str("%s [%s]", data.mDescription,
+ kFlushTypeNames[aFlushType]);
+ PROFILER_MARKER_TEXT(
+ "RefreshObserver", GRAPHICS,
+ MarkerOptions(MarkerStack::TakeBacktrace(std::move(data.mCause)),
+ MarkerTiming::IntervalUntilNowFrom(data.mRegisterTime),
+ std::move(data.mInnerWindowId)),
+ str);
+ }
+
+ array.RemoveElementAt(index);
+#ifdef DEBUG
+ aObserver->mRegistrationCount--;
+ MOZ_ASSERT(aObserver->mRegistrationCount >= 0,
+ "Registration count shouldn't be able to go negative");
+#endif
+ return true;
+}
+
+void nsRefreshDriver::AddTimerAdjustmentObserver(
+ nsATimerAdjustmentObserver* aObserver) {
+ MOZ_ASSERT(!mTimerAdjustmentObservers.Contains(aObserver));
+ mTimerAdjustmentObservers.AppendElement(aObserver);
+}
+
+void nsRefreshDriver::RemoveTimerAdjustmentObserver(
+ nsATimerAdjustmentObserver* aObserver) {
+ MOZ_ASSERT(mTimerAdjustmentObservers.Contains(aObserver));
+ mTimerAdjustmentObservers.RemoveElement(aObserver);
+}
+
+void nsRefreshDriver::PostVisualViewportResizeEvent(
+ VVPResizeEvent* aResizeEvent) {
+ mVisualViewportResizeEvents.AppendElement(aResizeEvent);
+ EnsureTimerStarted();
+}
+
+void nsRefreshDriver::DispatchVisualViewportResizeEvents() {
+ // We're taking a hint from scroll events and only dispatch the current set
+ // of queued resize events. If additional events are posted in response to
+ // the current events being dispatched, we'll dispatch them on the next tick.
+ VisualViewportResizeEventArray events =
+ std::move(mVisualViewportResizeEvents);
+ for (auto& event : events) {
+ event->Run();
+ }
+}
+
+void nsRefreshDriver::PostScrollEvent(mozilla::Runnable* aScrollEvent,
+ bool aDelayed) {
+ if (aDelayed) {
+ mDelayedScrollEvents.AppendElement(aScrollEvent);
+ } else {
+ mScrollEvents.AppendElement(aScrollEvent);
+ EnsureTimerStarted();
+ }
+}
+
+void nsRefreshDriver::DispatchScrollEvents() {
+ // Scroll events are one-shot, so after running them we can drop them.
+ // However, dispatching a scroll event can potentially cause more scroll
+ // events to be posted, so we move the initial set into a temporary array
+ // first. (Newly posted scroll events will be dispatched on the next tick.)
+ ScrollEventArray events = std::move(mScrollEvents);
+ for (auto& event : events) {
+ event->Run();
+ }
+}
+
+void nsRefreshDriver::PostVisualViewportScrollEvent(
+ VVPScrollEvent* aScrollEvent) {
+ mVisualViewportScrollEvents.AppendElement(aScrollEvent);
+ EnsureTimerStarted();
+}
+
+void nsRefreshDriver::DispatchVisualViewportScrollEvents() {
+ // Scroll events are one-shot, so after running them we can drop them.
+ // However, dispatching a scroll event can potentially cause more scroll
+ // events to be posted, so we move the initial set into a temporary array
+ // first. (Newly posted scroll events will be dispatched on the next tick.)
+ VisualViewportScrollEventArray events =
+ std::move(mVisualViewportScrollEvents);
+ for (auto& event : events) {
+ event->Run();
+ }
+}
+
+// https://drafts.csswg.org/cssom-view/#evaluate-media-queries-and-report-changes
+void nsRefreshDriver::EvaluateMediaQueriesAndReportChanges() {
+ if (!mMightNeedMediaQueryListenerUpdate) {
+ return;
+ }
+ mMightNeedMediaQueryListenerUpdate = false;
+ if (!mPresContext) {
+ return;
+ }
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS(
+ "Evaluate media queries and report changes", LAYOUT);
+ RefPtr<Document> doc = mPresContext->Document();
+ doc->EvaluateMediaQueriesAndReportChanges(/* aRecurse = */ true);
+}
+
+void nsRefreshDriver::AddPostRefreshObserver(
+ nsAPostRefreshObserver* aObserver) {
+ MOZ_ASSERT(!mPostRefreshObservers.Contains(aObserver));
+ mPostRefreshObservers.AppendElement(aObserver);
+}
+
+void nsRefreshDriver::RemovePostRefreshObserver(
+ nsAPostRefreshObserver* aObserver) {
+ bool removed = mPostRefreshObservers.RemoveElement(aObserver);
+ MOZ_DIAGNOSTIC_ASSERT(removed);
+ Unused << removed;
+}
+
+void nsRefreshDriver::AddImageRequest(imgIRequest* aRequest) {
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay == 0) {
+ mRequests.Insert(aRequest);
+ } else {
+ auto* const start = mStartTable.GetOrInsertNew(delay);
+ start->mEntries.Insert(aRequest);
+ }
+
+ EnsureTimerStarted();
+
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsCOMPtr<nsIURI> uri = aRequest->GetURI();
+
+ PROFILER_MARKER_TEXT("Image Animation", GRAPHICS,
+ MarkerOptions(MarkerTiming::IntervalStart(),
+ MarkerInnerWindowIdFromDocShell(
+ GetDocShell(mPresContext))),
+ nsContentUtils::TruncatedURLForDisplay(uri));
+ }
+}
+
+void nsRefreshDriver::RemoveImageRequest(imgIRequest* aRequest) {
+ // Try to remove from both places, just in case.
+ bool removed = mRequests.EnsureRemoved(aRequest);
+ uint32_t delay = GetFirstFrameDelay(aRequest);
+ if (delay != 0) {
+ ImageStartData* start = mStartTable.Get(delay);
+ if (start) {
+ removed = removed | start->mEntries.EnsureRemoved(aRequest);
+ }
+ }
+
+ if (removed && profiler_thread_is_being_profiled_for_markers()) {
+ nsCOMPtr<nsIURI> uri = aRequest->GetURI();
+
+ PROFILER_MARKER_TEXT("Image Animation", GRAPHICS,
+ MarkerOptions(MarkerTiming::IntervalEnd(),
+ MarkerInnerWindowIdFromDocShell(
+ GetDocShell(mPresContext))),
+ nsContentUtils::TruncatedURLForDisplay(uri));
+ }
+}
+
+void nsRefreshDriver::NotifyDOMContentLoaded() {
+ // If the refresh driver is going to tick, we mark the timestamp after
+ // everything is flushed in the next tick. If it isn't, mark ourselves as
+ // flushed now.
+ if (!HasObservers()) {
+ if (nsPresContext* pc = GetPresContext()) {
+ pc->NotifyDOMContentFlushed();
+ }
+ // else, we don't have a nsPresContext, so our doc is probably being
+ // destroyed and this notification doesn't need sending anyway.
+ } else {
+ mNotifyDOMContentFlushed = true;
+ }
+}
+
+void nsRefreshDriver::RegisterCompositionPayload(
+ const mozilla::layers::CompositionPayload& aPayload) {
+ mCompositionPayloads.AppendElement(aPayload);
+}
+
+void nsRefreshDriver::AddForceNotifyContentfulPaintPresContext(
+ nsPresContext* aPresContext) {
+ mForceNotifyContentfulPaintPresContexts.AppendElement(aPresContext);
+}
+
+void nsRefreshDriver::FlushForceNotifyContentfulPaintPresContext() {
+ while (!mForceNotifyContentfulPaintPresContexts.IsEmpty()) {
+ WeakPtr<nsPresContext> presContext =
+ mForceNotifyContentfulPaintPresContexts.PopLastElement();
+ if (presContext) {
+ presContext->NotifyContentfulPaint();
+ }
+ }
+}
+
+void nsRefreshDriver::RunDelayedEventsSoon() {
+ // Place entries for delayed events into their corresponding normal list,
+ // and schedule a refresh. When these delayed events run, if their document
+ // still has events suppressed then they will be readded to the delayed
+ // events list.
+
+ mScrollEvents.AppendElements(mDelayedScrollEvents);
+ mDelayedScrollEvents.Clear();
+
+ mResizeEventFlushObservers.AppendElements(mDelayedResizeEventFlushObservers);
+ mDelayedResizeEventFlushObservers.Clear();
+
+ EnsureTimerStarted();
+}
+
+bool nsRefreshDriver::CanDoCatchUpTick() {
+ if (mTestControllingRefreshes || !mActiveTimer) {
+ return false;
+ }
+
+ // If we've already ticked for the current timer refresh (or more recently
+ // than that), then we don't need to do any catching up.
+ if (mMostRecentRefresh >= mActiveTimer->MostRecentRefresh()) {
+ return false;
+ }
+
+ if (mActiveTimer->IsBlocked()) {
+ return false;
+ }
+
+ if (mTickVsyncTime.IsNull()) {
+ // Don't try to run a catch-up tick before there has been at least one
+ // normal tick. The catch-up tick could negatively affect page load
+ // performance.
+ return false;
+ }
+
+ if (mPresContext && mPresContext->Document()->GetReadyStateEnum() <
+ Document::READYSTATE_COMPLETE) {
+ // Don't try to run a catch-up tick before the page has finished loading.
+ // The catch-up tick could negatively affect page load performance.
+ return false;
+ }
+
+ return true;
+}
+
+bool nsRefreshDriver::CanDoExtraTick() {
+ // Only allow one extra tick per normal vsync tick.
+ if (mAttemptedExtraTickSinceLastVsync) {
+ return false;
+ }
+
+ // If we don't have a timer, or we didn't tick on the timer's
+ // refresh then we can't do an 'extra' tick (but we may still
+ // do a catch up tick).
+ if (!mActiveTimer ||
+ mActiveTimer->MostRecentRefresh() != mMostRecentRefresh) {
+ return false;
+ }
+
+ // Grab the current timestamp before checking the tick hint to be sure
+ // sure that it's equal or smaller than the value used within checking
+ // the tick hint.
+ TimeStamp now = TimeStamp::Now();
+ Maybe<TimeStamp> nextTick = mActiveTimer->GetNextTickHint();
+ int32_t minimumRequiredTime = StaticPrefs::layout_extra_tick_minimum_ms();
+ // If there's less than 4 milliseconds until the next tick, it's probably
+ // not worth trying to catch up.
+ if (minimumRequiredTime < 0 || !nextTick ||
+ (*nextTick - now) < TimeDuration::FromMilliseconds(minimumRequiredTime)) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsRefreshDriver::EnsureTimerStarted(EnsureTimerStartedFlags aFlags) {
+ // FIXME: Bug 1346065: We should also assert the case where we have no
+ // stylo-threads.
+ MOZ_ASSERT(!ServoStyleSet::IsInServoTraversal() || NS_IsMainThread(),
+ "EnsureTimerStarted should be called only when we are not "
+ "in servo traversal or on the main-thread");
+
+ if (mTestControllingRefreshes) return;
+
+ if (!mRefreshTimerStartedCause) {
+ mRefreshTimerStartedCause = profiler_capture_backtrace();
+ }
+
+ // will it already fire, and no other changes needed?
+ if (mActiveTimer && !(aFlags & eForceAdjustTimer)) {
+ // If we're being called from within a user input handler, and we think
+ // there's time to rush an extra tick immediately, then schedule a runnable
+ // to run the extra tick.
+ if (mUserInputProcessingCount && CanDoExtraTick()) {
+ RefPtr<nsRefreshDriver> self = this;
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction(
+ "RefreshDriver::EnsureTimerStarted::extra",
+ [self]() -> void {
+ // Re-check if we can still do an extra tick, in case anything
+ // changed while the runnable was pending.
+ if (self->CanDoExtraTick()) {
+ PROFILER_MARKER_UNTYPED("ExtraRefreshDriverTick", GRAPHICS);
+ LOG("[%p] Doing extra tick for user input", self.get());
+ self->mAttemptedExtraTickSinceLastVsync = true;
+ self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
+ self->mActiveTimer->MostRecentRefresh(),
+ IsExtraTick::Yes);
+ }
+ }),
+ EventQueuePriority::Vsync);
+ }
+ return;
+ }
+
+ if (IsFrozen() || !mPresContext) {
+ // If we don't want to start it now, or we've been disconnected.
+ StopTimer();
+ return;
+ }
+
+ if (mPresContext->Document()->IsBeingUsedAsImage()) {
+ // Image documents receive ticks from clients' refresh drivers.
+ // XXXdholbert Exclude SVG-in-opentype fonts from this optimization, until
+ // they receive refresh-driver ticks from their client docs (bug 1107252).
+ if (!mPresContext->Document()->IsSVGGlyphsDocument()) {
+ MOZ_ASSERT(!mActiveTimer,
+ "image doc refresh driver should never have its own timer");
+ return;
+ }
+ }
+
+ // We got here because we're either adjusting the time *or* we're
+ // starting it for the first time. Add to the right timer,
+ // prehaps removing it from a previously-set one.
+ RefreshDriverTimer* newTimer = ChooseTimer();
+ if (newTimer != mActiveTimer) {
+ if (mActiveTimer) mActiveTimer->RemoveRefreshDriver(this);
+ mActiveTimer = newTimer;
+ mActiveTimer->AddRefreshDriver(this);
+
+ if (!mHasStartedTimerAtLeastOnce) {
+ mHasStartedTimerAtLeastOnce = true;
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ nsCString text = "initial timer start "_ns;
+ if (mPresContext->Document()->GetDocumentURI()) {
+ text.Append(nsContentUtils::TruncatedURLForDisplay(
+ mPresContext->Document()->GetDocumentURI()));
+ }
+
+ PROFILER_MARKER_TEXT("nsRefreshDriver", LAYOUT,
+ MarkerOptions(MarkerInnerWindowIdFromDocShell(
+ GetDocShell(mPresContext))),
+ text);
+ }
+ }
+
+ // If the timer has ticked since we last ticked, consider doing a 'catch-up'
+ // tick immediately.
+ if (CanDoCatchUpTick()) {
+ RefPtr<nsRefreshDriver> self = this;
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction(
+ "RefreshDriver::EnsureTimerStarted::catch-up",
+ [self]() -> void {
+ // Re-check if we can still do a catch-up, in case anything
+ // changed while the runnable was pending.
+ if (self->CanDoCatchUpTick()) {
+ LOG("[%p] Doing catch up tick", self.get());
+ self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
+ self->mActiveTimer->MostRecentRefresh());
+ }
+ }),
+ EventQueuePriority::Vsync);
+ }
+ }
+
+ // When switching from an inactive timer to an active timer, the root
+ // refresh driver is skipped due to being set to the content refresh
+ // driver's timestamp. In case of EnsureTimerStarted is called from
+ // ScheduleViewManagerFlush, we should avoid this behavior to flush
+ // a paint in the same tick on the root refresh driver.
+ if (aFlags & eNeverAdjustTimer) {
+ return;
+ }
+
+ // Since the different timers are sampled at different rates, when switching
+ // timers, the most recent refresh of the new timer may be *before* the
+ // most recent refresh of the old timer.
+ // If we are restoring the refresh driver from test control, the time is
+ // expected to go backwards (see bug 1043078), otherwise we just keep the most
+ // recent tick of this driver (which may be older than the most recent tick of
+ // the timer).
+ if (!(aFlags & eAllowTimeToGoBackwards)) {
+ return;
+ }
+
+ if (mMostRecentRefresh != mActiveTimer->MostRecentRefresh()) {
+ mMostRecentRefresh = mActiveTimer->MostRecentRefresh();
+
+ for (nsATimerAdjustmentObserver* obs :
+ mTimerAdjustmentObservers.EndLimitedRange()) {
+ obs->NotifyTimerAdjusted(mMostRecentRefresh);
+ }
+ }
+}
+
+void nsRefreshDriver::StopTimer() {
+ if (!mActiveTimer) return;
+
+ mActiveTimer->RemoveRefreshDriver(this);
+ mActiveTimer = nullptr;
+ mRefreshTimerStartedCause = nullptr;
+}
+
+uint32_t nsRefreshDriver::ObserverCount() const {
+ uint32_t sum = 0;
+ for (const ObserverArray& array : mObservers) {
+ sum += array.Length();
+ }
+
+ // Even while throttled, we need to process layout and style changes. Style
+ // changes can trigger transitions which fire events when they complete, and
+ // layout changes can affect media queries on child documents, triggering
+ // style changes, etc.
+ sum += mAnimationEventFlushObservers.Length();
+ sum += mResizeEventFlushObservers.Length();
+ sum += mStyleFlushObservers.Length();
+ sum += mLayoutFlushObservers.Length();
+ sum += mPendingFullscreenEvents.Length();
+ sum += mFrameRequestCallbackDocs.Length();
+ sum += mThrottledFrameRequestCallbackDocs.Length();
+ sum += mViewManagerFlushIsPending;
+ sum += mEarlyRunners.Length();
+ sum += mTimerAdjustmentObservers.Length();
+ sum += mAutoFocusFlushDocuments.Length();
+ return sum;
+}
+
+bool nsRefreshDriver::HasObservers() const {
+ for (const ObserverArray& array : mObservers) {
+ if (!array.IsEmpty()) {
+ return true;
+ }
+ }
+
+ // We should NOT count mTimerAdjustmentObservers here since this method is
+ // used to determine whether or not to stop the timer or re-start it and timer
+ // adjustment observers should not influence timer starting or stopping.
+ return (mViewManagerFlushIsPending && !mThrottled) ||
+ !mStyleFlushObservers.IsEmpty() || !mLayoutFlushObservers.IsEmpty() ||
+ !mAnimationEventFlushObservers.IsEmpty() ||
+ !mResizeEventFlushObservers.IsEmpty() ||
+ !mPendingFullscreenEvents.IsEmpty() ||
+ !mFrameRequestCallbackDocs.IsEmpty() ||
+ !mThrottledFrameRequestCallbackDocs.IsEmpty() ||
+ !mAutoFocusFlushDocuments.IsEmpty() || !mEarlyRunners.IsEmpty();
+}
+
+void nsRefreshDriver::AppendObserverDescriptionsToString(
+ nsACString& aStr) const {
+ for (const ObserverArray& array : mObservers) {
+ for (const auto& observer : array.EndLimitedRange()) {
+ aStr.AppendPrintf("%s [%s], ", observer.mDescription,
+ kFlushTypeNames[observer.mFlushType]);
+ }
+ }
+ if (mViewManagerFlushIsPending && !mThrottled) {
+ aStr.AppendLiteral("View manager flush pending, ");
+ }
+ if (!mAnimationEventFlushObservers.IsEmpty()) {
+ aStr.AppendPrintf("%zux Animation event flush observer, ",
+ mAnimationEventFlushObservers.Length());
+ }
+ if (!mResizeEventFlushObservers.IsEmpty()) {
+ aStr.AppendPrintf("%zux Resize event flush observer, ",
+ mResizeEventFlushObservers.Length());
+ }
+ if (!mStyleFlushObservers.IsEmpty()) {
+ aStr.AppendPrintf("%zux Style flush observer, ",
+ mStyleFlushObservers.Length());
+ }
+ if (!mLayoutFlushObservers.IsEmpty()) {
+ aStr.AppendPrintf("%zux Layout flush observer, ",
+ mLayoutFlushObservers.Length());
+ }
+ if (!mPendingFullscreenEvents.IsEmpty()) {
+ aStr.AppendPrintf("%zux Pending fullscreen event, ",
+ mPendingFullscreenEvents.Length());
+ }
+ if (!mFrameRequestCallbackDocs.IsEmpty()) {
+ aStr.AppendPrintf("%zux Frame request callback doc, ",
+ mFrameRequestCallbackDocs.Length());
+ }
+ if (!mThrottledFrameRequestCallbackDocs.IsEmpty()) {
+ aStr.AppendPrintf("%zux Throttled frame request callback doc, ",
+ mThrottledFrameRequestCallbackDocs.Length());
+ }
+ if (!mAutoFocusFlushDocuments.IsEmpty()) {
+ aStr.AppendPrintf("%zux AutoFocus flush doc, ",
+ mAutoFocusFlushDocuments.Length());
+ }
+ if (!mEarlyRunners.IsEmpty()) {
+ aStr.AppendPrintf("%zux Early runner, ", mEarlyRunners.Length());
+ }
+ // Remove last ", "
+ aStr.Truncate(aStr.Length() - 2);
+}
+
+bool nsRefreshDriver::HasImageRequests() const {
+ for (const auto& data : mStartTable.Values()) {
+ if (!data->mEntries.IsEmpty()) {
+ return true;
+ }
+ }
+
+ return !mRequests.IsEmpty();
+}
+
+auto nsRefreshDriver::GetReasonsToTick() const -> TickReasons {
+ TickReasons reasons = TickReasons::eNone;
+ if (HasObservers()) {
+ reasons |= TickReasons::eHasObservers;
+ }
+ if (HasImageRequests() && !mThrottled) {
+ reasons |= TickReasons::eHasImageRequests;
+ }
+ if (mNeedToUpdateResizeObservers) {
+ reasons |= TickReasons::eNeedsToNotifyResizeObservers;
+ }
+ if (mNeedToUpdateIntersectionObservations) {
+ reasons |= TickReasons::eNeedsToUpdateIntersectionObservations;
+ }
+ if (mMightNeedMediaQueryListenerUpdate) {
+ reasons |= TickReasons::eHasPendingMediaQueryListeners;
+ }
+ if (mNeedToUpdateContentRelevancy) {
+ reasons |= TickReasons::eNeedsToUpdateContentRelevancy;
+ }
+ if (!mVisualViewportResizeEvents.IsEmpty()) {
+ reasons |= TickReasons::eHasVisualViewportResizeEvents;
+ }
+ if (!mScrollEvents.IsEmpty()) {
+ reasons |= TickReasons::eHasScrollEvents;
+ }
+ if (!mVisualViewportScrollEvents.IsEmpty()) {
+ reasons |= TickReasons::eHasVisualViewportScrollEvents;
+ }
+ if (mPresContext && mPresContext->IsRoot() &&
+ mPresContext->NeedsMoreTicksForUserInput()) {
+ reasons |= TickReasons::eRootNeedsMoreTicksForUserInput;
+ }
+ return reasons;
+}
+
+void nsRefreshDriver::AppendTickReasonsToString(TickReasons aReasons,
+ nsACString& aStr) const {
+ if (aReasons == TickReasons::eNone) {
+ aStr.AppendLiteral(" <none>");
+ return;
+ }
+
+ if (aReasons & TickReasons::eHasObservers) {
+ aStr.AppendLiteral(" HasObservers (");
+ AppendObserverDescriptionsToString(aStr);
+ aStr.AppendLiteral(")");
+ }
+ if (aReasons & TickReasons::eHasImageRequests) {
+ aStr.AppendLiteral(" HasImageAnimations");
+ }
+ if (aReasons & TickReasons::eNeedsToNotifyResizeObservers) {
+ aStr.AppendLiteral(" NeedsToNotifyResizeObservers");
+ }
+ if (aReasons & TickReasons::eNeedsToUpdateIntersectionObservations) {
+ aStr.AppendLiteral(" NeedsToUpdateIntersectionObservations");
+ }
+ if (aReasons & TickReasons::eHasPendingMediaQueryListeners) {
+ aStr.AppendLiteral(" HasPendingMediaQueryListeners");
+ }
+ if (aReasons & TickReasons::eNeedsToUpdateContentRelevancy) {
+ aStr.AppendLiteral(" NeedsToUpdateContentRelevancy");
+ }
+ if (aReasons & TickReasons::eHasVisualViewportResizeEvents) {
+ aStr.AppendLiteral(" HasVisualViewportResizeEvents");
+ }
+ if (aReasons & TickReasons::eHasScrollEvents) {
+ aStr.AppendLiteral(" HasScrollEvents");
+ }
+ if (aReasons & TickReasons::eHasVisualViewportScrollEvents) {
+ aStr.AppendLiteral(" HasVisualViewportScrollEvents");
+ }
+ if (aReasons & TickReasons::eRootNeedsMoreTicksForUserInput) {
+ aStr.AppendLiteral(" RootNeedsMoreTicksForUserInput");
+ }
+}
+
+bool nsRefreshDriver::
+ ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint() {
+ // On top level content pages keep the timer running initially so that we
+ // paint the page soon enough.
+ if (mThrottled || mTestControllingRefreshes || !XRE_IsContentProcess() ||
+ !mPresContext->Document()->IsTopLevelContentDocument() ||
+ mPresContext->Document()->IsInitialDocument() ||
+ gfxPlatform::IsInLayoutAsapMode() ||
+ mPresContext->HadFirstContentfulPaint() ||
+ mPresContext->Document()->GetReadyStateEnum() ==
+ Document::READYSTATE_COMPLETE) {
+ return false;
+ }
+ if (mBeforeFirstContentfulPaintTimerRunningLimit.IsNull()) {
+ // Don't let the timer to run forever, so limit to 4s for now.
+ mBeforeFirstContentfulPaintTimerRunningLimit =
+ TimeStamp::Now() + TimeDuration::FromSeconds(4.0f);
+ }
+
+ return TimeStamp::Now() <= mBeforeFirstContentfulPaintTimerRunningLimit;
+}
+
+bool nsRefreshDriver::ShouldKeepTimerRunningAfterPageLoad() {
+ if (mHasExceededAfterLoadTickPeriod ||
+ !StaticPrefs::layout_keep_ticking_after_load_ms() || mThrottled ||
+ mTestControllingRefreshes || !XRE_IsContentProcess() ||
+ !mPresContext->Document()->IsTopLevelContentDocument() ||
+ TaskController::Get()->PendingMainthreadTaskCountIncludingSuspended() ==
+ 0 ||
+ gfxPlatform::IsInLayoutAsapMode()) {
+ // Make the next check faster.
+ mHasExceededAfterLoadTickPeriod = true;
+ return false;
+ }
+
+ nsPIDOMWindowInner* innerWindow = mPresContext->Document()->GetInnerWindow();
+ if (!innerWindow) {
+ return false;
+ }
+ auto* perf =
+ static_cast<PerformanceMainThread*>(innerWindow->GetPerformance());
+ if (!perf) {
+ return false;
+ }
+ nsDOMNavigationTiming* timing = perf->GetDOMTiming();
+ if (!timing) {
+ return false;
+ }
+ TimeStamp loadend = timing->LoadEventEnd();
+ if (!loadend) {
+ return false;
+ }
+ // Keep ticking after the page load for some time.
+ const bool retval =
+ (loadend + TimeDuration::FromMilliseconds(
+ StaticPrefs::layout_keep_ticking_after_load_ms())) >
+ TimeStamp::Now();
+ if (!retval) {
+ mHasExceededAfterLoadTickPeriod = true;
+ }
+ return retval;
+}
+
+nsRefreshDriver::ObserverArray& nsRefreshDriver::ArrayFor(
+ FlushType aFlushType) {
+ switch (aFlushType) {
+ case FlushType::Event:
+ return mObservers[0];
+ case FlushType::Style:
+ case FlushType::Frames:
+ return mObservers[1];
+ case FlushType::Layout:
+ return mObservers[2];
+ case FlushType::Display:
+ return mObservers[3];
+ default:
+ MOZ_CRASH("We don't track refresh observers for this flush type");
+ }
+}
+
+/*
+ * nsITimerCallback implementation
+ */
+
+void nsRefreshDriver::DoTick() {
+ MOZ_ASSERT(!IsFrozen(), "Why are we notified while frozen?");
+ MOZ_ASSERT(mPresContext, "Why are we notified after disconnection?");
+ MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
+ "Shouldn't have a JSContext on the stack");
+
+ if (mTestControllingRefreshes) {
+ Tick(VsyncId(), mMostRecentRefresh);
+ } else {
+ Tick(VsyncId(), TimeStamp::Now());
+ }
+}
+
+struct DocumentFrameCallbacks {
+ explicit DocumentFrameCallbacks(Document* aDocument) : mDocument(aDocument) {}
+
+ RefPtr<Document> mDocument;
+ nsTArray<FrameRequest> mCallbacks;
+};
+
+static void TakeFrameRequestCallbacksFrom(
+ Document* aDocument, nsTArray<DocumentFrameCallbacks>& aTarget) {
+ aTarget.AppendElement(aDocument);
+ aDocument->TakeFrameRequestCallbacks(aTarget.LastElement().mCallbacks);
+}
+
+void nsRefreshDriver::ScheduleAutoFocusFlush(Document* aDocument) {
+ MOZ_ASSERT(!mAutoFocusFlushDocuments.Contains(aDocument));
+ mAutoFocusFlushDocuments.AppendElement(aDocument);
+ EnsureTimerStarted();
+}
+
+void nsRefreshDriver::FlushAutoFocusDocuments() {
+ nsTArray<RefPtr<Document>> docs(std::move(mAutoFocusFlushDocuments));
+
+ for (const auto& doc : docs) {
+ MOZ_KnownLive(doc)->FlushAutoFocusCandidates();
+ }
+}
+
+void nsRefreshDriver::MaybeIncreaseMeasuredTicksSinceLoading() {
+ if (mPresContext && mPresContext->IsRoot()) {
+ mPresContext->MaybeIncreaseMeasuredTicksSinceLoading();
+ }
+}
+
+void nsRefreshDriver::CancelFlushAutoFocus(Document* aDocument) {
+ mAutoFocusFlushDocuments.RemoveElement(aDocument);
+}
+
+// https://fullscreen.spec.whatwg.org/#run-the-fullscreen-steps
+void nsRefreshDriver::RunFullscreenSteps() {
+ // Swap out the current pending events
+ nsTArray<UniquePtr<PendingFullscreenEvent>> pendings(
+ std::move(mPendingFullscreenEvents));
+ for (UniquePtr<PendingFullscreenEvent>& event : pendings) {
+ event->Dispatch();
+ }
+}
+
+void nsRefreshDriver::UpdateIntersectionObservations(TimeStamp aNowTime) {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Compute intersections", LAYOUT);
+
+ AutoTArray<RefPtr<Document>, 32> documents;
+
+ if (mPresContext->Document()->HasIntersectionObservers()) {
+ documents.AppendElement(mPresContext->Document());
+ }
+
+ mPresContext->Document()->CollectDescendantDocuments(
+ documents, [](const Document* document) -> bool {
+ return document->HasIntersectionObservers();
+ });
+
+ for (const auto& doc : documents) {
+ doc->UpdateIntersectionObservations(aNowTime);
+ doc->ScheduleIntersectionObserverNotification();
+ }
+
+ mNeedToUpdateIntersectionObservations = false;
+}
+
+void nsRefreshDriver::UpdateRelevancyOfContentVisibilityAutoFrames() {
+ if (!mNeedToUpdateContentRelevancy) {
+ return;
+ }
+
+ if (RefPtr<PresShell> topLevelPresShell = mPresContext->GetPresShell()) {
+ topLevelPresShell->UpdateRelevancyOfContentVisibilityAutoFrames();
+ }
+
+ mPresContext->Document()->EnumerateSubDocuments([](Document& aSubDoc) {
+ if (PresShell* presShell = aSubDoc.GetPresShell()) {
+ presShell->UpdateRelevancyOfContentVisibilityAutoFrames();
+ }
+ return CallState::Continue;
+ });
+
+ mNeedToUpdateContentRelevancy = false;
+}
+
+void nsRefreshDriver::DetermineProximityToViewportAndNotifyResizeObservers() {
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("Update the rendering: step 14", LAYOUT);
+ // NotifyResizeObservers might re-schedule us for next tick.
+ mNeedToUpdateResizeObservers = false;
+
+ if (MOZ_UNLIKELY(!mPresContext)) {
+ return;
+ }
+
+ auto ShouldCollect = [](const Document* aDocument) {
+ PresShell* ps = aDocument->GetPresShell();
+ if (!ps || !ps->DidInitialize()) {
+ // If there's no shell or it didn't initialize, then we'll run this code
+ // when the pres shell does the initial reflow.
+ return false;
+ }
+ return ps->HasContentVisibilityAutoFrames() ||
+ aDocument->HasResizeObservers();
+ };
+
+ AutoTArray<RefPtr<Document>, 32> documents;
+ if (ShouldCollect(mPresContext->Document())) {
+ documents.AppendElement(mPresContext->Document());
+ }
+ mPresContext->Document()->CollectDescendantDocuments(documents,
+ ShouldCollect);
+
+ for (const RefPtr<Document>& doc : documents) {
+ MOZ_KnownLive(doc)->DetermineProximityToViewportAndNotifyResizeObservers();
+ }
+}
+
+void nsRefreshDriver::DispatchAnimationEvents() {
+ if (!mPresContext) {
+ return;
+ }
+
+ // Hold all AnimationEventDispatcher in mAnimationEventFlushObservers as
+ // a RefPtr<> array since each AnimationEventDispatcher might be destroyed
+ // during processing the previous dispatcher.
+ AutoTArray<RefPtr<AnimationEventDispatcher>, 16> dispatchers;
+ dispatchers.AppendElements(mAnimationEventFlushObservers);
+ mAnimationEventFlushObservers.Clear();
+
+ for (auto& dispatcher : dispatchers) {
+ dispatcher->DispatchEvents();
+ }
+}
+
+void nsRefreshDriver::RunFrameRequestCallbacks(TimeStamp aNowTime) {
+ // Grab all of our frame request callbacks up front.
+ nsTArray<DocumentFrameCallbacks> frameRequestCallbacks(
+ mFrameRequestCallbackDocs.Length() +
+ mThrottledFrameRequestCallbackDocs.Length());
+
+ // First, grab throttled frame request callbacks.
+ {
+ nsTArray<Document*> docsToRemove;
+
+ // We always tick throttled frame requests if the entire refresh driver is
+ // throttled, because in that situation throttled frame requests tick at the
+ // same frequency as non-throttled frame requests.
+ bool tickThrottledFrameRequests = mThrottled;
+
+ if (!tickThrottledFrameRequests &&
+ aNowTime >= mNextThrottledFrameRequestTick) {
+ mNextThrottledFrameRequestTick =
+ aNowTime + mThrottledFrameRequestInterval;
+ tickThrottledFrameRequests = true;
+ }
+
+ for (Document* doc : mThrottledFrameRequestCallbackDocs) {
+ if (tickThrottledFrameRequests) {
+ // We're ticking throttled documents, so grab this document's requests.
+ // We don't bother appending to docsToRemove because we're going to
+ // clear mThrottledFrameRequestCallbackDocs anyway.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ } else if (!doc->ShouldThrottleFrameRequests()) {
+ // This document is no longer throttled, so grab its requests even
+ // though we're not ticking throttled frame requests right now. If
+ // this is the first unthrottled document with frame requests, we'll
+ // enter high precision mode the next time the callback is scheduled.
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ docsToRemove.AppendElement(doc);
+ }
+ }
+
+ // Remove all the documents we're ticking from
+ // mThrottledFrameRequestCallbackDocs so they can be readded as needed.
+ if (tickThrottledFrameRequests) {
+ mThrottledFrameRequestCallbackDocs.Clear();
+ } else {
+ // XXX(seth): We're using this approach to avoid concurrent modification
+ // of mThrottledFrameRequestCallbackDocs. docsToRemove usually has either
+ // zero elements or a very small number, so this should be OK in practice.
+ for (Document* doc : docsToRemove) {
+ mThrottledFrameRequestCallbackDocs.RemoveElement(doc);
+ }
+ }
+ }
+
+ // Now grab unthrottled frame request callbacks.
+ for (Document* doc : mFrameRequestCallbackDocs) {
+ TakeFrameRequestCallbacksFrom(doc, frameRequestCallbacks);
+ }
+
+ // Reset mFrameRequestCallbackDocs so they can be readded as needed.
+ mFrameRequestCallbackDocs.Clear();
+
+ if (!frameRequestCallbacks.IsEmpty()) {
+ AUTO_PROFILER_TRACING_MARKER_DOCSHELL("Paint",
+ "requestAnimationFrame callbacks",
+ GRAPHICS, GetDocShell(mPresContext));
+ for (const DocumentFrameCallbacks& docCallbacks : frameRequestCallbacks) {
+ TimeStamp startTime = TimeStamp::Now();
+
+ // XXXbz Bug 863140: GetInnerWindow can return the outer
+ // window in some cases.
+ nsPIDOMWindowInner* innerWindow =
+ docCallbacks.mDocument->GetInnerWindow();
+ DOMHighResTimeStamp timeStamp = 0;
+ if (innerWindow) {
+ if (Performance* perf = innerWindow->GetPerformance()) {
+ timeStamp = perf->TimeStampToDOMHighResForRendering(aNowTime);
+ }
+ // else window is partially torn down already
+ }
+ for (auto& callback : docCallbacks.mCallbacks) {
+ if (docCallbacks.mDocument->IsCanceledFrameRequestCallback(
+ callback.mHandle)) {
+ continue;
+ }
+
+ nsCOMPtr<nsIGlobalObject> global(innerWindow ? innerWindow->AsGlobal()
+ : nullptr);
+ CallbackDebuggerNotificationGuard guard(
+ global, DebuggerNotificationType::RequestAnimationFrameCallback);
+
+ // MOZ_KnownLive is OK, because the stack array frameRequestCallbacks
+ // keeps callback alive and the mCallback strong reference can't be
+ // mutated by the call.
+ LogFrameRequestCallback::Run run(callback.mCallback);
+ MOZ_KnownLive(callback.mCallback)->Call(timeStamp);
+ }
+
+ if (docCallbacks.mDocument->GetReadyStateEnum() ==
+ Document::READYSTATE_COMPLETE) {
+ glean::performance_responsiveness::req_anim_frame_callback
+ .AccumulateRawDuration(TimeStamp::Now() - startTime);
+ } else {
+ glean::performance_pageload::req_anim_frame_callback
+ .AccumulateRawDuration(TimeStamp::Now() - startTime);
+ }
+ }
+ }
+}
+
+static StaticAutoPtr<AutoTArray<RefPtr<Task>, 8>> sPendingIdleTasks;
+
+void nsRefreshDriver::DispatchIdleTaskAfterTickUnlessExists(Task* aTask) {
+ if (!sPendingIdleTasks) {
+ sPendingIdleTasks = new AutoTArray<RefPtr<Task>, 8>();
+ } else {
+ if (sPendingIdleTasks->Contains(aTask)) {
+ return;
+ }
+ }
+
+ sPendingIdleTasks->AppendElement(aTask);
+}
+
+void nsRefreshDriver::CancelIdleTask(Task* aTask) {
+ if (!sPendingIdleTasks) {
+ return;
+ }
+
+ sPendingIdleTasks->RemoveElement(aTask);
+
+ if (sPendingIdleTasks->IsEmpty()) {
+ sPendingIdleTasks = nullptr;
+ }
+}
+
+static CallState ReduceAnimations(Document& aDocument) {
+ if (nsPresContext* pc = aDocument.GetPresContext()) {
+ if (pc->EffectCompositor()->NeedsReducing()) {
+ pc->EffectCompositor()->ReduceAnimations();
+ }
+ }
+ aDocument.EnumerateSubDocuments(ReduceAnimations);
+ return CallState::Continue;
+}
+
+bool nsRefreshDriver::TickObserverArray(uint32_t aIdx, TimeStamp aNowTime) {
+ for (RefPtr<nsARefreshObserver> obs : mObservers[aIdx].EndLimitedRange()) {
+ obs->WillRefresh(aNowTime);
+
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ return false;
+ }
+ }
+
+ // Any animation timelines updated above may cause animations to queue
+ // Promise resolution microtasks. We shouldn't run these, however, until we
+ // have fully updated the animation state.
+ //
+ // As per the "update animations and send events" procedure[1], we should
+ // remove replaced animations and then run these microtasks before
+ // dispatching the corresponding animation events.
+ //
+ // [1]
+ // https://drafts.csswg.org/web-animations-1/#update-animations-and-send-events
+ if (aIdx == 1) {
+ // This is the FlushType::Style case.
+ {
+ nsAutoMicroTask mt;
+ ReduceAnimations(*mPresContext->Document());
+ }
+
+ // Check if running the microtask checkpoint caused the pres context to
+ // be destroyed.
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ return false;
+ }
+
+ FlushAutoFocusDocuments();
+ DispatchScrollEvents();
+ DispatchVisualViewportScrollEvents();
+ EvaluateMediaQueriesAndReportChanges();
+ DispatchAnimationEvents();
+ RunFullscreenSteps();
+ RunFrameRequestCallbacks(aNowTime);
+ MaybeIncreaseMeasuredTicksSinceLoading();
+
+ if (mPresContext && mPresContext->GetPresShell()) {
+ AutoTArray<PresShell*, 16> observers;
+ observers.AppendElements(mStyleFlushObservers);
+ for (uint32_t j = observers.Length();
+ j && mPresContext && mPresContext->GetPresShell(); --j) {
+ // Make sure to not process observers which might have been removed
+ // during previous iterations.
+ PresShell* rawPresShell = observers[j - 1];
+ if (!mStyleFlushObservers.RemoveElement(rawPresShell)) {
+ continue;
+ }
+
+ LogPresShellObserver::Run run(rawPresShell, this);
+
+ RefPtr<PresShell> presShell = rawPresShell;
+ presShell->mObservingStyleFlushes = false;
+ presShell->FlushPendingNotifications(
+ ChangesToFlush(FlushType::Style, false));
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to (though it might still be waiting on
+ // a layout flush).
+ presShell->NotifyFontFaceSetOnRefresh();
+ mNeedToRecomputeVisibility = true;
+
+ // Record the telemetry for events that occurred between ticks.
+ presShell->PingPerTickTelemetry(FlushType::Style);
+ }
+ }
+ } else if (aIdx == 2) {
+ // This is the FlushType::Layout case.
+ AutoTArray<PresShell*, 16> observers;
+ observers.AppendElements(mLayoutFlushObservers);
+ for (uint32_t j = observers.Length();
+ j && mPresContext && mPresContext->GetPresShell(); --j) {
+ // Make sure to not process observers which might have been removed
+ // during previous iterations.
+ PresShell* rawPresShell = observers[j - 1];
+ if (!mLayoutFlushObservers.RemoveElement(rawPresShell)) {
+ continue;
+ }
+
+ LogPresShellObserver::Run run(rawPresShell, this);
+
+ RefPtr<PresShell> presShell = rawPresShell;
+ presShell->mObservingLayoutFlushes = false;
+ presShell->mWasLastReflowInterrupted = false;
+ const ChangesToFlush ctf(FlushType::InterruptibleLayout, false);
+ presShell->FlushPendingNotifications(ctf);
+ if (presShell->FixUpFocus()) {
+ presShell->FlushPendingNotifications(ctf);
+ }
+
+ // Inform the FontFaceSet that we ticked, so that it can resolve its
+ // ready promise if it needs to.
+ presShell->NotifyFontFaceSetOnRefresh();
+ mNeedToRecomputeVisibility = true;
+
+ // Record the telemetry for events that occurred between ticks.
+ presShell->PingPerTickTelemetry(FlushType::Layout);
+ }
+ }
+
+ // The pres context may be destroyed during we do the flushing.
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ return false;
+ }
+
+ return true;
+}
+
+void nsRefreshDriver::Tick(VsyncId aId, TimeStamp aNowTime,
+ IsExtraTick aIsExtraTick /* = No */) {
+ MOZ_ASSERT(!nsContentUtils::GetCurrentJSContext(),
+ "Shouldn't have a JSContext on the stack");
+
+ // We're either frozen or we were disconnected (likely in the middle
+ // of a tick iteration). Just do nothing here, since our
+ // prescontext went away.
+ if (IsFrozen() || !mPresContext) {
+ return;
+ }
+
+ // We can have a race condition where the vsync timestamp
+ // is before the most recent refresh due to a forced refresh.
+ // The underlying assumption is that the refresh driver tick can only
+ // go forward in time, not backwards. To prevent the refresh
+ // driver from going back in time, just skip this tick and
+ // wait until the next tick.
+ // If this is an 'extra' tick, then we expect it to be using the same
+ // vsync id and timestamp as the original tick, so also allow those.
+ if ((aNowTime <= mMostRecentRefresh) && !mTestControllingRefreshes &&
+ aIsExtraTick == IsExtraTick::No) {
+ return;
+ }
+ auto cleanupInExtraTick = MakeScopeExit([&] { mInNormalTick = false; });
+ mInNormalTick = aIsExtraTick != IsExtraTick::Yes;
+
+ bool isPresentingInVR = false;
+#if defined(MOZ_WIDGET_ANDROID)
+ isPresentingInVR = gfx::VRManagerChild::IsPresenting();
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+ if (!isPresentingInVR && IsWaitingForPaint(aNowTime)) {
+ // In immersive VR mode, we do not get notifications when frames are
+ // presented, so we do not wait for the compositor in that mode.
+
+ // We're currently suspended waiting for earlier Tick's to
+ // be completed (on the Compositor). Mark that we missed the paint
+ // and keep waiting.
+ PROFILER_MARKER_UNTYPED(
+ "RefreshDriverTick waiting for paint", GRAPHICS,
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)));
+ return;
+ }
+
+ const TimeStamp previousRefresh = mMostRecentRefresh;
+ mMostRecentRefresh = aNowTime;
+
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
+ mRootRefresh = nullptr;
+ }
+ mSkippedPaints = false;
+
+ RefPtr<PresShell> presShell = mPresContext->GetPresShell();
+ if (!presShell) {
+ StopTimer();
+ return;
+ }
+
+ TickReasons tickReasons = GetReasonsToTick();
+ if (tickReasons == TickReasons::eNone) {
+ // We no longer have any observers.
+ // Discard composition payloads because there is no paint.
+ mCompositionPayloads.Clear();
+
+ // We don't want to stop the timer when observers are initially
+ // removed, because sometimes observers can be added and removed
+ // often depending on what other things are going on and in that
+ // situation we don't want to thrash our timer. So instead we
+ // wait until we get a Notify() call when we have no observers
+ // before stopping the timer.
+ // On top level content pages keep the timer running initially so that we
+ // paint the page soon enough.
+ if (ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint()) {
+ PROFILER_MARKER(
+ "RefreshDriverTick waiting for first contentful paint", GRAPHICS,
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
+ "Paint");
+ } else if (ShouldKeepTimerRunningAfterPageLoad()) {
+ PROFILER_MARKER(
+ "RefreshDriverTick after page load", GRAPHICS,
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)), Tracing,
+ "Paint");
+ } else {
+ StopTimer();
+ }
+ return;
+ }
+
+ if (StaticPrefs::layout_skip_ticks_while_page_suspended()) {
+ Document* doc = mPresContext->Document();
+ nsPIDOMWindowInner* win = doc ? doc->GetInnerWindow() : nullptr;
+ // Synchronous DOM operations mark the document being in such. Window's
+ // suspend can be used also by external code. So we check here them both
+ // in order to limit rAF skipping to only those synchronous DOM APIs which
+ // also suspend window.
+ if (win && win->IsSuspended() && doc->IsInSyncOperation()) {
+ return;
+ }
+ }
+
+ AUTO_PROFILER_LABEL_RELEVANT_FOR_JS("RefreshDriver tick", LAYOUT);
+
+ nsAutoCString profilerStr;
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ profilerStr.AppendLiteral("Tick reasons:");
+ AppendTickReasonsToString(tickReasons, profilerStr);
+ }
+ AUTO_PROFILER_MARKER_TEXT(
+ "RefreshDriverTick", GRAPHICS,
+ MarkerOptions(
+ MarkerStack::TakeBacktrace(std::move(mRefreshTimerStartedCause)),
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext))),
+ profilerStr);
+
+ mResizeSuppressed = false;
+
+ bool oldInRefresh = mInRefresh;
+ auto restoreInRefresh = MakeScopeExit([&] { mInRefresh = oldInRefresh; });
+ mInRefresh = true;
+
+ AutoRestore<TimeStamp> restoreTickStart(mTickStart);
+ mTickStart = TimeStamp::Now();
+ mTickVsyncId = aId;
+ mTickVsyncTime = aNowTime;
+
+ gfxPlatform::GetPlatform()->SchedulePaintIfDeviceReset();
+
+ FlushForceNotifyContentfulPaintPresContext();
+
+ AutoTArray<nsCOMPtr<nsIRunnable>, 16> earlyRunners = std::move(mEarlyRunners);
+ for (auto& runner : earlyRunners) {
+ runner->Run();
+ // Early runners might destroy this pres context.
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ }
+
+ // Resize events should be fired before layout flushes or
+ // calling animation frame callbacks.
+ AutoTArray<RefPtr<PresShell>, 16> observers;
+ observers.AppendElements(mResizeEventFlushObservers);
+ for (RefPtr<PresShell>& presShell : Reversed(observers)) {
+ if (!mPresContext || !mPresContext->GetPresShell()) {
+ StopTimer();
+ return;
+ }
+ // Make sure to not process observers which might have been removed
+ // during previous iterations.
+ if (!mResizeEventFlushObservers.RemoveElement(presShell)) {
+ continue;
+ }
+ // MOZ_KnownLive because 'observers' is guaranteed to
+ // keep it alive.
+ //
+ // Fixing https://bugzilla.mozilla.org/show_bug.cgi?id=1620312 on its own
+ // won't help here, because 'observers' is non-const and we have the
+ // Reversed() going on too...
+ MOZ_KnownLive(presShell)->FireResizeEvent();
+ }
+ DispatchVisualViewportResizeEvents();
+
+ /*
+ * The timer holds a reference to |this| while calling |Notify|.
+ * However, implementations of |WillRefresh| are permitted to destroy
+ * the pres context, which will cause our |mPresContext| to become
+ * null. If this happens, TickObserverArray will tell us by returning
+ * false, and we must stop notifying observers.
+ */
+ // XXXdholbert This would be cleaner as a loop, but for now it's helpful to
+ // have these calls separated out, so that we can figure out which
+ // observer-category is involved from the backtrace of crash reports.
+ bool keepGoing = true;
+ MOZ_ASSERT(ArrayLength(mObservers) == 4,
+ "if this changes, then we need to add or remove calls to "
+ "TickObserverArray below");
+ keepGoing = keepGoing && TickObserverArray(0, aNowTime);
+ keepGoing = keepGoing && TickObserverArray(1, aNowTime);
+ keepGoing = keepGoing && TickObserverArray(2, aNowTime);
+ keepGoing = keepGoing && TickObserverArray(3, aNowTime);
+ if (!keepGoing) {
+ StopTimer();
+ return;
+ }
+
+ // Recompute approximate frame visibility if it's necessary and enough time
+ // has passed since the last time we did it.
+ if (mNeedToRecomputeVisibility && !mThrottled &&
+ aNowTime >= mNextRecomputeVisibilityTick &&
+ !presShell->IsPaintingSuppressed()) {
+ mNextRecomputeVisibilityTick = aNowTime + mMinRecomputeVisibilityInterval;
+ mNeedToRecomputeVisibility = false;
+
+ presShell->ScheduleApproximateFrameVisibilityUpdateNow();
+ }
+
+ // Update any popups that may need to be moved or hidden due to their
+ // anchor changing.
+ if (nsXULPopupManager* pm = nsXULPopupManager::GetInstance()) {
+ pm->UpdatePopupPositions(this);
+ }
+
+ // Update the relevancy of the content of any `content-visibility: auto`
+ // elements. The specification says: "Specifically, such changes will
+ // take effect between steps 13 and 14 of Update the Rendering step of
+ // the Processing Model (between “run the animation frame callbacks†and
+ // “run the update intersection observations stepsâ€)."
+ // https://drafts.csswg.org/css-contain/#cv-notes
+ UpdateRelevancyOfContentVisibilityAutoFrames();
+
+ // Step 14 (https://html.spec.whatwg.org/#update-the-rendering).
+ // 1) Initial proximity to the viewport determination for
+ // content-visibility:auto elements and 2) Resize observers notifications.
+ DetermineProximityToViewportAndNotifyResizeObservers();
+ if (MOZ_UNLIKELY(!mPresContext || !mPresContext->GetPresShell())) {
+ // A resize observer callback apparently destroyed our PresContext.
+ StopTimer();
+ return;
+ }
+
+ UpdateIntersectionObservations(aNowTime);
+
+ UpdateAnimatedImages(previousRefresh, aNowTime);
+
+ bool dispatchTasksAfterTick = false;
+ if (mViewManagerFlushIsPending && !mThrottled) {
+ nsCString transactionId;
+ if (profiler_thread_is_being_profiled_for_markers()) {
+ transactionId.AppendLiteral("Transaction ID: ");
+ transactionId.AppendInt((uint64_t)mNextTransactionId);
+ }
+ AUTO_PROFILER_MARKER_TEXT(
+ "ViewManagerFlush", GRAPHICS,
+ MarkerOptions(
+ MarkerInnerWindowIdFromDocShell(GetDocShell(mPresContext)),
+ MarkerStack::TakeBacktrace(std::move(mViewManagerFlushCause))),
+ transactionId);
+
+ // Forward our composition payloads to the layer manager.
+ if (!mCompositionPayloads.IsEmpty()) {
+ nsCOMPtr<nsIWidget> widget = mPresContext->GetRootWidget();
+ WindowRenderer* renderer = widget ? widget->GetWindowRenderer() : nullptr;
+ if (renderer && renderer->AsWebRender()) {
+ renderer->AsWebRender()->RegisterPayloads(mCompositionPayloads);
+ }
+ mCompositionPayloads.Clear();
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Starting ProcessPendingUpdates\n");
+ }
+#endif
+
+ mViewManagerFlushIsPending = false;
+ RefPtr<nsViewManager> vm = mPresContext->GetPresShell()->GetViewManager();
+ const bool skipPaint = isPresentingInVR;
+ // Skip the paint in immersive VR mode because whatever we paint here will
+ // not end up on the screen. The screen is displaying WebGL content from a
+ // single canvas in that mode.
+ if (!skipPaint) {
+ PaintTelemetry::AutoRecordPaint record;
+ vm->ProcessPendingUpdates();
+ }
+
+#ifdef MOZ_DUMP_PAINTING
+ if (nsLayoutUtils::InvalidationDebuggingIsEnabled()) {
+ printf_stderr("Ending ProcessPendingUpdates\n");
+ }
+#endif
+
+ dispatchTasksAfterTick = true;
+ mHasScheduleFlush = false;
+ } else {
+ // No paint happened, discard composition payloads.
+ mCompositionPayloads.Clear();
+ }
+
+#ifndef ANDROID /* bug 1142079 */
+ double totalMs = (TimeStamp::Now() - mTickStart).ToMilliseconds();
+ mozilla::Telemetry::Accumulate(mozilla::Telemetry::REFRESH_DRIVER_TICK,
+ static_cast<uint32_t>(totalMs));
+#endif
+
+ if (mNotifyDOMContentFlushed) {
+ mNotifyDOMContentFlushed = false;
+ mPresContext->NotifyDOMContentFlushed();
+ }
+
+ for (nsAPostRefreshObserver* observer :
+ mPostRefreshObservers.ForwardRange()) {
+ observer->DidRefresh();
+ }
+
+ NS_ASSERTION(mInRefresh, "Still in refresh");
+
+ if (mPresContext->IsRoot() && XRE_IsContentProcess() &&
+ StaticPrefs::gfx_content_always_paint()) {
+ ScheduleViewManagerFlush();
+ }
+
+ if (dispatchTasksAfterTick && sPendingIdleTasks) {
+ UniquePtr<AutoTArray<RefPtr<Task>, 8>> tasks(sPendingIdleTasks.forget());
+ for (RefPtr<Task>& taskWithDelay : *tasks) {
+ TaskController::Get()->AddTask(taskWithDelay.forget());
+ }
+ }
+}
+
+void nsRefreshDriver::UpdateAnimatedImages(TimeStamp aPreviousRefresh,
+ TimeStamp aNowTime) {
+ if (mThrottled) {
+ // Don't do this when throttled, as the compositor might be paused and we
+ // don't want to queue a lot of paints, see bug 1828587.
+ return;
+ }
+ // Perform notification to imgIRequests subscribed to listen for refresh
+ // events.
+ for (const auto& entry : mStartTable) {
+ const uint32_t& delay = entry.GetKey();
+ ImageStartData* data = entry.GetWeak();
+
+ if (data->mEntries.IsEmpty()) {
+ continue;
+ }
+
+ if (data->mStartTime) {
+ TimeStamp& start = *data->mStartTime;
+
+ if (aPreviousRefresh >= start && aNowTime >= start) {
+ TimeDuration prev = aPreviousRefresh - start;
+ TimeDuration curr = aNowTime - start;
+ uint32_t prevMultiple = uint32_t(prev.ToMilliseconds()) / delay;
+
+ // We want to trigger images' refresh if we've just crossed over a
+ // multiple of the first image's start time. If so, set the animation
+ // start time to the nearest multiple of the delay and move all the
+ // images in this table to the main requests table.
+ if (prevMultiple != uint32_t(curr.ToMilliseconds()) / delay) {
+ mozilla::TimeStamp desired =
+ start + TimeDuration::FromMilliseconds(prevMultiple * delay);
+ BeginRefreshingImages(data->mEntries, desired);
+ }
+ } else {
+ // Sometimes the start time can be in the future if we spin a nested
+ // event loop and re-entrantly tick. In that case, setting the
+ // animation start time to the start time seems like the least bad
+ // thing we can do.
+ mozilla::TimeStamp desired = start;
+ BeginRefreshingImages(data->mEntries, desired);
+ }
+ } else {
+ // This is the very first time we've drawn images with this time delay.
+ // Set the animation start time to "now" and move all the images in this
+ // table to the main requests table.
+ mozilla::TimeStamp desired = aNowTime;
+ BeginRefreshingImages(data->mEntries, desired);
+ data->mStartTime.emplace(aNowTime);
+ }
+ }
+
+ if (!mRequests.IsEmpty()) {
+ // RequestRefresh may run scripts, so it's not safe to directly call it
+ // while using a hashtable enumerator to enumerate mRequests in case
+ // script modifies the hashtable. Instead, we build a (local) array of
+ // images to refresh, and then we refresh each image in that array.
+ nsTArray<nsCOMPtr<imgIContainer>> imagesToRefresh(mRequests.Count());
+
+ for (const auto& req : mRequests) {
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ imagesToRefresh.AppendElement(image.forget());
+ }
+ }
+
+ for (const auto& image : imagesToRefresh) {
+ image->RequestRefresh(aNowTime);
+ }
+ }
+}
+
+void nsRefreshDriver::BeginRefreshingImages(RequestTable& aEntries,
+ mozilla::TimeStamp aDesired) {
+ for (const auto& req : aEntries) {
+ mRequests.Insert(req);
+
+ nsCOMPtr<imgIContainer> image;
+ if (NS_SUCCEEDED(req->GetImage(getter_AddRefs(image)))) {
+ image->SetAnimationStartTime(aDesired);
+ }
+ }
+ aEntries.Clear();
+}
+
+void nsRefreshDriver::Freeze() {
+ StopTimer();
+ mFreezeCount++;
+}
+
+void nsRefreshDriver::Thaw() {
+ NS_ASSERTION(mFreezeCount > 0, "Thaw() called on an unfrozen refresh driver");
+
+ if (mFreezeCount > 0) {
+ mFreezeCount--;
+ }
+
+ if (mFreezeCount == 0) {
+ if (HasObservers() || HasImageRequests()) {
+ // FIXME: This isn't quite right, since our EnsureTimerStarted call
+ // updates our mMostRecentRefresh, but the DoRefresh call won't run
+ // and notify our observers until we get back to the event loop.
+ // Thus MostRecentRefresh() will lie between now and the DoRefresh.
+ RefPtr<nsRunnableMethod<nsRefreshDriver>> event = NewRunnableMethod(
+ "nsRefreshDriver::DoRefresh", this, &nsRefreshDriver::DoRefresh);
+ nsPresContext* pc = GetPresContext();
+ if (pc) {
+ pc->Document()->Dispatch(event.forget());
+ EnsureTimerStarted();
+ } else {
+ NS_ERROR("Thawing while document is being destroyed");
+ }
+ }
+ }
+}
+
+void nsRefreshDriver::FinishedWaitingForTransaction() {
+ if (mSkippedPaints && !IsInRefresh() &&
+ (HasObservers() || HasImageRequests()) && CanDoCatchUpTick()) {
+ NS_DispatchToCurrentThreadQueue(
+ NS_NewRunnableFunction(
+ "nsRefreshDriver::FinishedWaitingForTransaction",
+ [self = RefPtr{this}]() {
+ if (self->CanDoCatchUpTick()) {
+ self->Tick(self->mActiveTimer->MostRecentRefreshVsyncId(),
+ self->mActiveTimer->MostRecentRefresh());
+ }
+ }),
+ EventQueuePriority::Vsync);
+ }
+ mWaitingForTransaction = false;
+ mSkippedPaints = false;
+}
+
+mozilla::layers::TransactionId nsRefreshDriver::GetTransactionId(
+ bool aThrottle) {
+ mNextTransactionId = mNextTransactionId.Next();
+ LOG("[%p] Allocating transaction id %" PRIu64, this, mNextTransactionId.mId);
+
+ // If this a paint from within a normal tick, and the caller hasn't explicitly
+ // asked for it to skip being throttled, then record this transaction as
+ // pending and maybe disable painting until some transactions are processed.
+ if (aThrottle && mInNormalTick) {
+ mPendingTransactions.AppendElement(mNextTransactionId);
+ if (TooManyPendingTransactions() && !mWaitingForTransaction &&
+ !mTestControllingRefreshes) {
+ LOG("[%p] Hit max pending transaction limit, entering wait mode", this);
+ mWaitingForTransaction = true;
+ mSkippedPaints = false;
+ }
+ }
+
+ return mNextTransactionId;
+}
+
+mozilla::layers::TransactionId nsRefreshDriver::LastTransactionId() const {
+ return mNextTransactionId;
+}
+
+void nsRefreshDriver::RevokeTransactionId(
+ mozilla::layers::TransactionId aTransactionId) {
+ MOZ_ASSERT(aTransactionId == mNextTransactionId);
+ LOG("[%p] Revoking transaction id %" PRIu64, this, aTransactionId.mId);
+ if (AtPendingTransactionLimit() &&
+ mPendingTransactions.Contains(aTransactionId) && mWaitingForTransaction) {
+ LOG("[%p] No longer over pending transaction limit, leaving wait state",
+ this);
+ MOZ_ASSERT(!mSkippedPaints,
+ "How did we skip a paint when we're in the middle of one?");
+ FinishedWaitingForTransaction();
+ }
+
+ // Notify the pres context so that it can deliver MozAfterPaint for this
+ // id if any caller was expecting it.
+ nsPresContext* pc = GetPresContext();
+ if (pc) {
+ pc->NotifyRevokingDidPaint(aTransactionId);
+ }
+ // Remove aTransactionId from the set of outstanding transactions since we're
+ // no longer waiting on it to be completed, but don't revert
+ // mNextTransactionId since we can't use the id again.
+ mPendingTransactions.RemoveElement(aTransactionId);
+}
+
+void nsRefreshDriver::ClearPendingTransactions() {
+ LOG("[%p] ClearPendingTransactions", this);
+ mPendingTransactions.Clear();
+ mWaitingForTransaction = false;
+}
+
+void nsRefreshDriver::ResetInitialTransactionId(
+ mozilla::layers::TransactionId aTransactionId) {
+ mNextTransactionId = aTransactionId;
+}
+
+mozilla::TimeStamp nsRefreshDriver::GetTransactionStart() { return mTickStart; }
+
+VsyncId nsRefreshDriver::GetVsyncId() { return mTickVsyncId; }
+
+mozilla::TimeStamp nsRefreshDriver::GetVsyncStart() { return mTickVsyncTime; }
+
+void nsRefreshDriver::NotifyTransactionCompleted(
+ mozilla::layers::TransactionId aTransactionId) {
+ LOG("[%p] Completed transaction id %" PRIu64, this, aTransactionId.mId);
+ mPendingTransactions.RemoveElement(aTransactionId);
+ if (mWaitingForTransaction && !TooManyPendingTransactions()) {
+ LOG("[%p] No longer over pending transaction limit, leaving wait state",
+ this);
+ FinishedWaitingForTransaction();
+ }
+}
+
+void nsRefreshDriver::WillRefresh(mozilla::TimeStamp aTime) {
+ mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
+ mRootRefresh = nullptr;
+ if (mSkippedPaints) {
+ DoRefresh();
+ }
+}
+
+bool nsRefreshDriver::IsWaitingForPaint(mozilla::TimeStamp aTime) {
+ if (mTestControllingRefreshes) {
+ return false;
+ }
+
+ if (mWaitingForTransaction) {
+ LOG("[%p] Over max pending transaction limit when trying to paint, "
+ "skipping",
+ this);
+ mSkippedPaints = true;
+ return true;
+ }
+
+ // Try find the 'root' refresh driver for the current window and check
+ // if that is waiting for a paint.
+ nsPresContext* pc = GetPresContext();
+ nsPresContext* rootContext = pc ? pc->GetRootPresContext() : nullptr;
+ if (rootContext) {
+ nsRefreshDriver* rootRefresh = rootContext->RefreshDriver();
+ if (rootRefresh && rootRefresh != this) {
+ if (rootRefresh->IsWaitingForPaint(aTime)) {
+ if (mRootRefresh != rootRefresh) {
+ if (mRootRefresh) {
+ mRootRefresh->RemoveRefreshObserver(this, FlushType::Style);
+ }
+ rootRefresh->AddRefreshObserver(this, FlushType::Style,
+ "Waiting for paint");
+ mRootRefresh = rootRefresh;
+ }
+ mSkippedPaints = true;
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void nsRefreshDriver::SetActivity(bool aIsActive) {
+ const bool shouldThrottle = !aIsActive;
+ if (mThrottled == shouldThrottle) {
+ return;
+ }
+ mThrottled = shouldThrottle;
+ if (mActiveTimer || GetReasonsToTick() != TickReasons::eNone) {
+ // We want to switch our timer type here, so just stop and restart the
+ // timer.
+ EnsureTimerStarted(eForceAdjustTimer);
+ }
+}
+
+nsPresContext* nsRefreshDriver::GetPresContext() const { return mPresContext; }
+
+void nsRefreshDriver::DoRefresh() {
+ // Don't do a refresh unless we're in a state where we should be refreshing.
+ if (!IsFrozen() && mPresContext && mActiveTimer) {
+ DoTick();
+ }
+}
+
+#ifdef DEBUG
+bool nsRefreshDriver::IsRefreshObserver(nsARefreshObserver* aObserver,
+ FlushType aFlushType) {
+ ObserverArray& array = ArrayFor(aFlushType);
+ return array.Contains(aObserver);
+}
+#endif
+
+void nsRefreshDriver::ScheduleViewManagerFlush() {
+ NS_ASSERTION(mPresContext && mPresContext->IsRoot(),
+ "Should only schedule view manager flush on root prescontexts");
+ mViewManagerFlushIsPending = true;
+ if (!mViewManagerFlushCause) {
+ mViewManagerFlushCause = profiler_capture_backtrace();
+ }
+ mHasScheduleFlush = true;
+ EnsureTimerStarted(eNeverAdjustTimer);
+}
+
+void nsRefreshDriver::ScheduleFrameRequestCallbacks(Document* aDocument) {
+ NS_ASSERTION(mFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mFrameRequestCallbackDocs.NoIndex &&
+ mThrottledFrameRequestCallbackDocs.IndexOf(aDocument) ==
+ mThrottledFrameRequestCallbackDocs.NoIndex,
+ "Don't schedule the same document multiple times");
+ if (aDocument->ShouldThrottleFrameRequests()) {
+ mThrottledFrameRequestCallbackDocs.AppendElement(aDocument);
+ } else {
+ mFrameRequestCallbackDocs.AppendElement(aDocument);
+ }
+
+ // make sure that the timer is running
+ EnsureTimerStarted();
+}
+
+void nsRefreshDriver::RevokeFrameRequestCallbacks(Document* aDocument) {
+ mFrameRequestCallbackDocs.RemoveElement(aDocument);
+ mThrottledFrameRequestCallbackDocs.RemoveElement(aDocument);
+ // No need to worry about restarting our timer in slack mode if it's already
+ // running; that will happen automatically when it fires.
+}
+
+void nsRefreshDriver::ScheduleFullscreenEvent(
+ UniquePtr<PendingFullscreenEvent> aEvent) {
+ mPendingFullscreenEvents.AppendElement(std::move(aEvent));
+ // make sure that the timer is running
+ EnsureTimerStarted();
+}
+
+void nsRefreshDriver::CancelPendingFullscreenEvents(Document* aDocument) {
+ for (auto i : Reversed(IntegerRange(mPendingFullscreenEvents.Length()))) {
+ if (mPendingFullscreenEvents[i]->Document() == aDocument) {
+ mPendingFullscreenEvents.RemoveElementAt(i);
+ }
+ }
+}
+
+void nsRefreshDriver::CancelPendingAnimationEvents(
+ AnimationEventDispatcher* aDispatcher) {
+ MOZ_ASSERT(aDispatcher);
+ aDispatcher->ClearEventQueue();
+ mAnimationEventFlushObservers.RemoveElement(aDispatcher);
+}
+
+/* static */
+TimeStamp nsRefreshDriver::GetIdleDeadlineHint(TimeStamp aDefault,
+ IdleCheck aCheckType) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!aDefault.IsNull());
+
+ // For computing idleness of refresh drivers we only care about
+ // sRegularRateTimerList, since we consider refresh drivers attached to
+ // sThrottledRateTimer to be inactive. This implies that tasks
+ // resulting from a tick on the sRegularRateTimer counts as being
+ // busy but tasks resulting from a tick on sThrottledRateTimer
+ // counts as being idle.
+ if (sRegularRateTimer) {
+ TimeStamp retVal = sRegularRateTimer->GetIdleDeadlineHint(aDefault);
+ if (retVal != aDefault) {
+ return retVal;
+ }
+ }
+
+ TimeStamp hint = TimeStamp();
+ if (sRegularRateTimerList) {
+ for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
+ TimeStamp newHint = timer->GetIdleDeadlineHint(aDefault);
+ if (newHint < aDefault && (hint.IsNull() || newHint < hint)) {
+ hint = newHint;
+ }
+ }
+ }
+
+ if (!hint.IsNull()) {
+ return hint;
+ }
+
+ if (aCheckType == IdleCheck::AllVsyncListeners && XRE_IsParentProcess()) {
+ Maybe<TimeDuration> maybeRate =
+ mozilla::gfx::VsyncSource::GetFastestVsyncRate();
+ if (maybeRate.isSome()) {
+ TimeDuration minIdlePeriod =
+ TimeDuration::FromMilliseconds(StaticPrefs::idle_period_min());
+ TimeDuration layoutIdleLimit = TimeDuration::FromMilliseconds(
+ StaticPrefs::layout_idle_period_time_limit());
+ TimeDuration rate = *maybeRate - layoutIdleLimit;
+
+ // If the rate is very short, don't let it affect idle processing in the
+ // parent process too much.
+ rate = std::max(rate, minIdlePeriod + minIdlePeriod);
+
+ TimeStamp newHint = TimeStamp::Now() + rate;
+ if (newHint < aDefault) {
+ return newHint;
+ }
+ }
+ }
+
+ return aDefault;
+}
+
+/* static */
+Maybe<TimeStamp> nsRefreshDriver::GetNextTickHint() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sRegularRateTimer) {
+ return sRegularRateTimer->GetNextTickHint();
+ }
+
+ Maybe<TimeStamp> hint = Nothing();
+ if (sRegularRateTimerList) {
+ for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
+ if (Maybe<TimeStamp> newHint = timer->GetNextTickHint()) {
+ if (!hint || newHint.value() < hint.value()) {
+ hint = newHint;
+ }
+ }
+ }
+ }
+ return hint;
+}
+
+/* static */
+bool nsRefreshDriver::IsRegularRateTimerTicking() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (sRegularRateTimer) {
+ if (sRegularRateTimer->IsTicking()) {
+ return true;
+ }
+ }
+
+ if (sRegularRateTimerList) {
+ for (RefreshDriverTimer* timer : *sRegularRateTimerList) {
+ if (timer->IsTicking()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void nsRefreshDriver::Disconnect() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ StopTimer();
+
+ mEarlyRunners.Clear();
+
+ if (mPresContext) {
+ mPresContext = nullptr;
+ if (--sRefreshDriverCount == 0) {
+ Shutdown();
+ }
+ }
+}
+
+#undef LOG
diff --git a/layout/base/nsRefreshDriver.h b/layout/base/nsRefreshDriver.h
new file mode 100644
index 0000000000..cd050f7431
--- /dev/null
+++ b/layout/base/nsRefreshDriver.h
@@ -0,0 +1,735 @@
+/* -*- 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 to notify things that animate before a refresh, at an appropriate
+ * refresh rate. (Perhaps temporary, until replaced by compositor.)
+ */
+
+#ifndef nsRefreshDriver_h_
+#define nsRefreshDriver_h_
+
+#include "mozilla/FlushType.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WeakPtr.h"
+#include "nsTObserverArray.h"
+#include "nsTArray.h"
+#include "nsTHashSet.h"
+#include "nsClassHashtable.h"
+#include "nsHashKeys.h"
+#include "nsRefreshObservers.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/VisualViewport.h"
+#include "mozilla/layers/TransactionIdAllocator.h"
+#include "LayersTypes.h"
+
+#include "GeckoProfiler.h" // for ProfileChunkedBuffer
+
+class nsPresContext;
+
+class imgIRequest;
+class nsIRunnable;
+
+namespace mozilla {
+class AnimationEventDispatcher;
+class PendingFullscreenEvent;
+class PresShell;
+class RefreshDriverTimer;
+class Runnable;
+class Task;
+} // namespace mozilla
+
+class nsRefreshDriver final : public mozilla::layers::TransactionIdAllocator,
+ public nsARefreshObserver {
+ using Document = mozilla::dom::Document;
+ using TransactionId = mozilla::layers::TransactionId;
+ using VVPResizeEvent =
+ mozilla::dom::VisualViewport::VisualViewportResizeEvent;
+ using VVPScrollEvent =
+ mozilla::dom::VisualViewport::VisualViewportScrollEvent;
+ using LogPresShellObserver = mozilla::LogPresShellObserver;
+
+ public:
+ explicit nsRefreshDriver(nsPresContext* aPresContext);
+ ~nsRefreshDriver();
+
+ /**
+ * Methods for testing, exposed via nsIDOMWindowUtils. See
+ * nsIDOMWindowUtils.advanceTimeAndRefresh for description.
+ */
+ void AdvanceTimeAndRefresh(int64_t aMilliseconds);
+ void RestoreNormalRefresh();
+ void DoTick();
+ bool IsTestControllingRefreshesEnabled() const {
+ return mTestControllingRefreshes;
+ }
+
+ /**
+ * Return the time of the most recent refresh. This is intended to be
+ * used by callers who want to start an animation now and want to know
+ * what time to consider the start of the animation. (This helps
+ * ensure that multiple animations started during the same event off
+ * the main event loop have the same start time.)
+ */
+ mozilla::TimeStamp MostRecentRefresh(bool aEnsureTimerStarted = true) const;
+
+ /**
+ * Add / remove refresh observers.
+ * RemoveRefreshObserver returns true if aObserver was found.
+ *
+ * The flush type affects:
+ * + the order in which the observers are notified (lowest flush
+ * type to highest, in order registered)
+ * + (in the future) which observers are suppressed when the display
+ * doesn't require current position data or isn't currently
+ * painting, and, correspondingly, which get notified when there
+ * is a flush during such suppression
+ * and it must be FlushType::Style, FlushType::Layout, or FlushType::Display.
+ *
+ * The refresh driver does NOT own a reference to these observers;
+ * they must remove themselves before they are destroyed.
+ *
+ * The observer will be called even if there is no other activity.
+ */
+ void AddRefreshObserver(nsARefreshObserver* aObserver,
+ mozilla::FlushType aFlushType,
+ const char* aObserverDescription);
+ bool RemoveRefreshObserver(nsARefreshObserver* aObserver,
+ mozilla::FlushType aFlushType);
+ /**
+ * Add / remove an observer wants to know the time when the refresh driver
+ * updated the most recent refresh time due to its active timer changes.
+ */
+ void AddTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
+ void RemoveTimerAdjustmentObserver(nsATimerAdjustmentObserver* aObserver);
+
+ void PostVisualViewportResizeEvent(VVPResizeEvent* aResizeEvent);
+ void DispatchVisualViewportResizeEvents();
+
+ void PostScrollEvent(mozilla::Runnable* aScrollEvent, bool aDelayed = false);
+ void DispatchScrollEvents();
+
+ void PostVisualViewportScrollEvent(VVPScrollEvent* aScrollEvent);
+ void DispatchVisualViewportScrollEvents();
+
+ /**
+ * Add an observer that will be called after each refresh. The caller
+ * must remove the observer before it is deleted. This does not trigger
+ * refresh driver ticks.
+ */
+ void AddPostRefreshObserver(nsAPostRefreshObserver* aObserver);
+ void AddPostRefreshObserver(mozilla::ManagedPostRefreshObserver*) = delete;
+ void RemovePostRefreshObserver(nsAPostRefreshObserver* aObserver);
+ void RemovePostRefreshObserver(mozilla::ManagedPostRefreshObserver*) = delete;
+
+ /**
+ * Add/Remove imgIRequest versions of observers.
+ *
+ * These are used for hooking into the refresh driver for
+ * controlling animated images.
+ *
+ * @note The refresh driver owns a reference to these listeners.
+ *
+ * @note Technically, imgIRequest objects are not nsARefreshObservers, but
+ * for controlling animated image repaint events, we subscribe the
+ * imgIRequests to the nsRefreshDriver for notification of paint events.
+ */
+ void AddImageRequest(imgIRequest* aRequest);
+ void RemoveImageRequest(imgIRequest* aRequest);
+
+ /**
+ * Marks that we're currently in the middle of processing user input.
+ * Called by EventDispatcher when it's handling an input event.
+ */
+ void EnterUserInputProcessing() { mUserInputProcessingCount++; }
+ void ExitUserInputProcessing() {
+ MOZ_ASSERT(mUserInputProcessingCount > 0);
+ mUserInputProcessingCount--;
+ }
+
+ /**
+ * Add / remove presshells which have pending resize event.
+ */
+ void AddResizeEventFlushObserver(mozilla::PresShell* aPresShell,
+ bool aDelayed = false) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ !mResizeEventFlushObservers.Contains(aPresShell) &&
+ !mDelayedResizeEventFlushObservers.Contains(aPresShell),
+ "Double-adding resize event flush observer");
+ if (aDelayed) {
+ mDelayedResizeEventFlushObservers.AppendElement(aPresShell);
+ } else {
+ mResizeEventFlushObservers.AppendElement(aPresShell);
+ EnsureTimerStarted();
+ }
+ }
+
+ void RemoveResizeEventFlushObserver(mozilla::PresShell* aPresShell) {
+ mResizeEventFlushObservers.RemoveElement(aPresShell);
+ mDelayedResizeEventFlushObservers.RemoveElement(aPresShell);
+ }
+
+ /**
+ * Add / remove presshells that we should flush style and layout on
+ */
+ void AddStyleFlushObserver(mozilla::PresShell* aPresShell) {
+ MOZ_DIAGNOSTIC_ASSERT(!mStyleFlushObservers.Contains(aPresShell),
+ "Double-adding style flush observer");
+ LogPresShellObserver::LogDispatch(aPresShell, this);
+ mStyleFlushObservers.AppendElement(aPresShell);
+ EnsureTimerStarted();
+ }
+
+ void RemoveStyleFlushObserver(mozilla::PresShell* aPresShell) {
+ mStyleFlushObservers.RemoveElement(aPresShell);
+ }
+ void AddLayoutFlushObserver(mozilla::PresShell* aPresShell) {
+ MOZ_DIAGNOSTIC_ASSERT(!IsLayoutFlushObserver(aPresShell),
+ "Double-adding layout flush observer");
+ LogPresShellObserver::LogDispatch(aPresShell, this);
+ mLayoutFlushObservers.AppendElement(aPresShell);
+ EnsureTimerStarted();
+ }
+ void RemoveLayoutFlushObserver(mozilla::PresShell* aPresShell) {
+ mLayoutFlushObservers.RemoveElement(aPresShell);
+ }
+
+ bool IsLayoutFlushObserver(mozilla::PresShell* aPresShell) {
+ return mLayoutFlushObservers.Contains(aPresShell);
+ }
+
+ /**
+ * "Early Runner" runnables will be called as the first step when refresh
+ * driver tick is triggered. Runners shouldn't keep other objects alive,
+ * since it isn't guaranteed they will ever get called.
+ */
+ void AddEarlyRunner(nsIRunnable* aRunnable) {
+ mEarlyRunners.AppendElement(aRunnable);
+ EnsureTimerStarted();
+ }
+
+ /**
+ * Remember whether our presshell's view manager needs a flush
+ */
+ void ScheduleViewManagerFlush();
+ void RevokeViewManagerFlush() { mViewManagerFlushIsPending = false; }
+ bool ViewManagerFlushIsPending() { return mViewManagerFlushIsPending; }
+ bool HasScheduleFlush() { return mHasScheduleFlush; }
+ void ClearHasScheduleFlush() { mHasScheduleFlush = false; }
+
+ /**
+ * Add a document for which we have FrameRequestCallbacks
+ */
+ void ScheduleFrameRequestCallbacks(Document* aDocument);
+
+ /**
+ * Remove a document for which we have FrameRequestCallbacks
+ */
+ void RevokeFrameRequestCallbacks(Document* aDocument);
+
+ /**
+ * Queue a new fullscreen event to be dispatched in next tick before
+ * the style flush
+ */
+ void ScheduleFullscreenEvent(
+ mozilla::UniquePtr<mozilla::PendingFullscreenEvent> aEvent);
+
+ /**
+ * Cancel all pending fullscreen events scheduled by ScheduleFullscreenEvent
+ * which targets any node in aDocument.
+ */
+ void CancelPendingFullscreenEvents(Document* aDocument);
+
+ /**
+ * Queue new animation events to dispatch in next tick.
+ */
+ void ScheduleAnimationEventDispatch(
+ mozilla::AnimationEventDispatcher* aDispatcher) {
+ NS_ASSERTION(!mAnimationEventFlushObservers.Contains(aDispatcher),
+ "Double-adding animation event flush observer");
+ mAnimationEventFlushObservers.AppendElement(aDispatcher);
+ EnsureTimerStarted();
+ }
+
+ /**
+ * Cancel all pending animation events associated with |aDispatcher|.
+ */
+ void CancelPendingAnimationEvents(
+ mozilla::AnimationEventDispatcher* aDispatcher);
+
+ /**
+ * Schedule a frame visibility update "soon", subject to the heuristics and
+ * throttling we apply to visibility updates.
+ */
+ void ScheduleFrameVisibilityUpdate() { mNeedToRecomputeVisibility = true; }
+
+ /**
+ * Tell the refresh driver that it is done driving refreshes and
+ * should stop its timer and forget about its pres context. This may
+ * be called from within a refresh.
+ */
+ void Disconnect();
+
+ bool IsFrozen() const { return mFreezeCount > 0; }
+
+ bool IsThrottled() const { return mThrottled; }
+
+ /**
+ * Freeze the refresh driver. It should stop delivering future
+ * refreshes until thawed. Note that the number of calls to Freeze() must
+ * match the number of calls to Thaw() in order for the refresh driver to
+ * be un-frozen.
+ */
+ void Freeze();
+
+ /**
+ * Thaw the refresh driver. If the number of calls to Freeze() matches the
+ * number of calls to this function, the refresh driver should start
+ * delivering refreshes again.
+ */
+ void Thaw();
+
+ /**
+ * Throttle or unthrottle the refresh driver. This is done if the
+ * corresponding presshell is hidden or shown.
+ */
+ void SetActivity(bool aIsActive);
+
+ /**
+ * Return the prescontext we were initialized with
+ */
+ nsPresContext* GetPresContext() const;
+
+ void CreateVsyncRefreshTimer();
+
+#ifdef DEBUG
+ /**
+ * Check whether the given observer is an observer for the given flush type
+ */
+ bool IsRefreshObserver(nsARefreshObserver* aObserver,
+ mozilla::FlushType aFlushType);
+#endif
+
+ /**
+ * Default interval the refresh driver uses, in ms.
+ */
+ static int32_t DefaultInterval();
+
+ /**
+ * Returns 1.0 if a recent rate wasn't smaller than
+ * DefaultInterval(). Otherwise return rate / DefaultInterval();
+ * So the return value is (0-1].
+ *
+ */
+ static double HighRateMultiplier();
+
+ bool IsInRefresh() { return mInRefresh; }
+
+ void SetIsResizeSuppressed() { mResizeSuppressed = true; }
+ bool IsResizeSuppressed() const { return mResizeSuppressed; }
+
+ // mozilla::layers::TransactionIdAllocator
+ TransactionId GetTransactionId(bool aThrottle) override;
+ TransactionId LastTransactionId() const override;
+ void NotifyTransactionCompleted(TransactionId aTransactionId) override;
+ void RevokeTransactionId(TransactionId aTransactionId) override;
+ void ClearPendingTransactions() override;
+ void ResetInitialTransactionId(TransactionId aTransactionId) override;
+ mozilla::TimeStamp GetTransactionStart() override;
+ mozilla::VsyncId GetVsyncId() override;
+ mozilla::TimeStamp GetVsyncStart() override;
+
+ bool IsWaitingForPaint(mozilla::TimeStamp aTime);
+
+ void ScheduleAutoFocusFlush(Document* aDocument);
+
+ // nsARefreshObserver
+ NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
+ return TransactionIdAllocator::AddRef();
+ }
+ NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
+ return TransactionIdAllocator::Release();
+ }
+ virtual void WillRefresh(mozilla::TimeStamp aTime) override;
+
+ /**
+ * Compute the time when the currently active refresh driver timer
+ * will start its next tick.
+ *
+ * Expects a non-null default value that is the upper bound of the
+ * expected deadline. If the next expected deadline is later than
+ * the default value, the default value is returned.
+ *
+ * If we're animating and we have skipped paints a time in the past
+ * is returned.
+ *
+ * If aCheckType is AllVsyncListeners and we're in the parent process,
+ * which doesn't have a RefreshDriver ticking, but some other process does
+ * have, the return value is
+ * (now + refreshrate - layout.idle_period.time_limit) as an approximation
+ * when something will happen.
+ * This can be useful check when parent process tries to avoid having too
+ * long idle periods for example when it is sending input events to an
+ * active child process.
+ */
+ enum IdleCheck { OnlyThisProcessRefreshDriver, AllVsyncListeners };
+ static mozilla::TimeStamp GetIdleDeadlineHint(mozilla::TimeStamp aDefault,
+ IdleCheck aCheckType);
+
+ /**
+ * It returns the expected timestamp of the next tick or nothing if the next
+ * tick is missed.
+ */
+ static mozilla::Maybe<mozilla::TimeStamp> GetNextTickHint();
+
+ static bool IsRegularRateTimerTicking();
+
+ static void DispatchIdleTaskAfterTickUnlessExists(mozilla::Task* aTask);
+ static void CancelIdleTask(mozilla::Task* aTask);
+
+ void NotifyDOMContentLoaded();
+
+ // Schedule a refresh so that any delayed events will run soon.
+ void RunDelayedEventsSoon();
+
+ void InitializeTimer() {
+ MOZ_ASSERT(!mActiveTimer);
+ EnsureTimerStarted();
+ }
+
+ bool HasPendingTick() const { return mActiveTimer; }
+
+ void EnsureIntersectionObservationsUpdateHappens() {
+ // This is enough to make sure that UpdateIntersectionObservations runs at
+ // least once. This is presumably the intent of step 5 in [1]:
+ //
+ // Schedule an iteration of the event loop in the root's browsing
+ // context.
+ //
+ // Though the wording of it is not quite clear to me...
+ //
+ // [1]:
+ // https://w3c.github.io/IntersectionObserver/#dom-intersectionobserver-observe
+ EnsureTimerStarted();
+ mNeedToUpdateIntersectionObservations = true;
+ }
+
+ void EnsureResizeObserverUpdateHappens() {
+ EnsureTimerStarted();
+ mNeedToUpdateResizeObservers = true;
+ }
+
+ void ScheduleMediaQueryListenerUpdate() {
+ EnsureTimerStarted();
+ mMightNeedMediaQueryListenerUpdate = true;
+ }
+
+ void EnsureContentRelevancyUpdateHappens() {
+ EnsureTimerStarted();
+ mNeedToUpdateContentRelevancy = true;
+ }
+
+ // Register a composition payload that will be forwarded to the layer manager
+ // if the current or upcoming refresh tick does a paint.
+ // If no paint happens, the payload is discarded.
+ // Should only be called on root refresh drivers.
+ void RegisterCompositionPayload(
+ const mozilla::layers::CompositionPayload& aPayload);
+
+ enum class TickReasons : uint32_t {
+ eNone = 0,
+ eHasObservers = 1 << 0,
+ eHasImageRequests = 1 << 1,
+ eNeedsToUpdateIntersectionObservations = 1 << 2,
+ eNeedsToUpdateContentRelevancy = 1 << 3,
+ eHasVisualViewportResizeEvents = 1 << 4,
+ eHasScrollEvents = 1 << 5,
+ eHasVisualViewportScrollEvents = 1 << 6,
+ eHasPendingMediaQueryListeners = 1 << 7,
+ eNeedsToNotifyResizeObservers = 1 << 8,
+ eRootNeedsMoreTicksForUserInput = 1 << 9,
+ };
+
+ void AddForceNotifyContentfulPaintPresContext(nsPresContext* aPresContext);
+ void FlushForceNotifyContentfulPaintPresContext();
+
+ // Mark that we've just run a tick from vsync, used to throttle 'extra'
+ // paints to one per vsync (see CanDoExtraTick).
+ void FinishedVsyncTick() { mAttemptedExtraTickSinceLastVsync = false; }
+
+ void CancelFlushAutoFocus(Document* aDocument);
+
+ private:
+ typedef nsTArray<RefPtr<VVPResizeEvent>> VisualViewportResizeEventArray;
+ typedef nsTArray<RefPtr<mozilla::Runnable>> ScrollEventArray;
+ typedef nsTArray<RefPtr<VVPScrollEvent>> VisualViewportScrollEventArray;
+ using RequestTable = nsTHashSet<RefPtr<imgIRequest>>;
+ struct ImageStartData {
+ ImageStartData() = default;
+
+ mozilla::Maybe<mozilla::TimeStamp> mStartTime;
+ RequestTable mEntries;
+ };
+ typedef nsClassHashtable<nsUint32HashKey, ImageStartData> ImageStartTable;
+
+ struct ObserverData {
+ nsARefreshObserver* mObserver;
+ const char* mDescription;
+ mozilla::TimeStamp mRegisterTime;
+ mozilla::MarkerInnerWindowId mInnerWindowId;
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> mCause;
+ mozilla::FlushType mFlushType;
+
+ bool operator==(nsARefreshObserver* aObserver) const {
+ return mObserver == aObserver;
+ }
+ operator RefPtr<nsARefreshObserver>() { return mObserver; }
+ };
+ using ObserverArray = nsTObserverArray<ObserverData>;
+ MOZ_CAN_RUN_SCRIPT
+ void FlushAutoFocusDocuments();
+ void RunFullscreenSteps();
+ void DispatchAnimationEvents();
+ MOZ_CAN_RUN_SCRIPT
+ void RunFrameRequestCallbacks(mozilla::TimeStamp aNowTime);
+ void UpdateIntersectionObservations(mozilla::TimeStamp aNowTime);
+ void UpdateRelevancyOfContentVisibilityAutoFrames();
+ MOZ_CAN_RUN_SCRIPT void
+ DetermineProximityToViewportAndNotifyResizeObservers();
+ void MaybeIncreaseMeasuredTicksSinceLoading();
+ void EvaluateMediaQueriesAndReportChanges();
+
+ enum class IsExtraTick {
+ No,
+ Yes,
+ };
+
+ // Helper for Tick, to call WillRefresh(aNowTime) on each entry in
+ // mObservers[aIdx] and then potentially do some additional post-notification
+ // work that's associated with the FlushType corresponding to aIdx.
+ //
+ // Returns true on success, or false if one of our calls has destroyed our
+ // pres context (in which case our callsite Tick() should immediately bail).
+ MOZ_CAN_RUN_SCRIPT
+ bool TickObserverArray(uint32_t aIdx, mozilla::TimeStamp aNowTime);
+
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY
+ void Tick(mozilla::VsyncId aId, mozilla::TimeStamp aNowTime,
+ IsExtraTick aIsExtraTick = IsExtraTick::No);
+
+ enum EnsureTimerStartedFlags {
+ eNone = 0,
+ eForceAdjustTimer = 1 << 0,
+ eAllowTimeToGoBackwards = 1 << 1,
+ eNeverAdjustTimer = 1 << 2,
+ };
+ void EnsureTimerStarted(EnsureTimerStartedFlags aFlags = eNone);
+ void StopTimer();
+
+ void UpdateThrottledState();
+
+ bool HasObservers() const;
+ void AppendObserverDescriptionsToString(nsACString& aStr) const;
+ // Note: This should only be called in the dtor of nsRefreshDriver.
+ uint32_t ObserverCount() const;
+ bool HasImageRequests() const;
+ bool ShouldKeepTimerRunningWhileWaitingForFirstContentfulPaint();
+ bool ShouldKeepTimerRunningAfterPageLoad();
+ ObserverArray& ArrayFor(mozilla::FlushType aFlushType);
+ // Trigger a refresh immediately, if haven't been disconnected or frozen.
+ void DoRefresh();
+
+ // Starts pending image animations, and refreshes ongoing animations.
+ void UpdateAnimatedImages(mozilla::TimeStamp aPreviousRefresh,
+ mozilla::TimeStamp aNowTime);
+
+ TickReasons GetReasonsToTick() const;
+ void AppendTickReasonsToString(TickReasons aReasons, nsACString& aStr) const;
+
+ double GetRegularTimerInterval() const;
+ static double GetThrottledTimerInterval();
+
+ static mozilla::TimeDuration GetMinRecomputeVisibilityInterval();
+
+ bool HaveFrameRequestCallbacks() const {
+ return mFrameRequestCallbackDocs.Length() != 0;
+ }
+
+ void FinishedWaitingForTransaction();
+
+ /**
+ * Returns true if we didn't tick on the most recent vsync, but we think
+ * we could run one now instead in order to reduce latency.
+ */
+ bool CanDoCatchUpTick();
+ /**
+ * Returns true if we think it's possible to run an repeat tick (between
+ * vsyncs) to hopefully replace the original tick's paint on the compositor.
+ * We allow this sometimes for tick requests coming for user input handling
+ * to reduce latency.
+ */
+ bool CanDoExtraTick();
+
+ bool AtPendingTransactionLimit() {
+ return mPendingTransactions.Length() == 2;
+ }
+ bool TooManyPendingTransactions() {
+ return mPendingTransactions.Length() >= 2;
+ }
+
+ mozilla::RefreshDriverTimer* ChooseTimer();
+ mozilla::RefreshDriverTimer* mActiveTimer;
+ RefPtr<mozilla::RefreshDriverTimer> mOwnTimer;
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> mRefreshTimerStartedCause;
+
+ // nsPresContext passed in constructor and unset in Disconnect.
+ mozilla::WeakPtr<nsPresContext> mPresContext;
+
+ RefPtr<nsRefreshDriver> mRootRefresh;
+
+ // The most recently allocated transaction id.
+ TransactionId mNextTransactionId;
+ AutoTArray<TransactionId, 3> mPendingTransactions;
+
+ uint32_t mFreezeCount;
+ uint32_t mUserInputProcessingCount = 0;
+
+ // How long we wait between ticks for throttled (which generally means
+ // non-visible) documents registered with a non-throttled refresh driver.
+ const mozilla::TimeDuration mThrottledFrameRequestInterval;
+
+ // How long we wait, at a minimum, before recomputing approximate frame
+ // visibility information. This is a minimum because, regardless of this
+ // interval, we only recompute visibility when we've seen a layout or style
+ // flush since the last time we did it.
+ const mozilla::TimeDuration mMinRecomputeVisibilityInterval;
+
+ mozilla::UniquePtr<mozilla::ProfileChunkedBuffer> mViewManagerFlushCause;
+
+ bool mThrottled : 1;
+ bool mNeedToRecomputeVisibility : 1;
+ bool mTestControllingRefreshes : 1;
+
+ // These two fields are almost the same, the only difference is that
+ // mViewManagerFlushIsPending gets cleared right before calling
+ // ProcessPendingUpdates, and mHasScheduleFlush gets cleared right after
+ // calling ProcessPendingUpdates. It is important that mHasScheduleFlush
+ // only gets cleared after, but it's not clear if mViewManagerFlushIsPending
+ // needs to be cleared before.
+ bool mViewManagerFlushIsPending : 1;
+ bool mHasScheduleFlush : 1;
+
+ bool mInRefresh : 1;
+
+ // True if the refresh driver is suspended waiting for transaction
+ // id's to be returned and shouldn't do any work during Tick().
+ bool mWaitingForTransaction : 1;
+ // True if Tick() was skipped because of mWaitingForTransaction and
+ // we should schedule a new Tick immediately when resumed instead
+ // of waiting until the next interval.
+ bool mSkippedPaints : 1;
+
+ // True if view managers should delay any resize request until the
+ // next tick by the refresh driver. This flag will be reset at the
+ // start of every tick.
+ bool mResizeSuppressed : 1;
+
+ // True if the next tick should notify DOMContentFlushed.
+ bool mNotifyDOMContentFlushed : 1;
+
+ // True if we need to flush in order to update intersection observations in
+ // all our documents.
+ bool mNeedToUpdateIntersectionObservations : 1;
+
+ // True if we need to flush in order to update intersection observations in
+ // all our documents.
+ bool mNeedToUpdateResizeObservers : 1;
+
+ // True if we might need to report media query changes in any of our
+ // documents.
+ bool mMightNeedMediaQueryListenerUpdate : 1;
+
+ // True if we need to update the relevancy of `content-visibility: auto`
+ // elements in our documents.
+ bool mNeedToUpdateContentRelevancy : 1;
+
+ // True if we're currently within the scope of Tick() handling a normal
+ // (timer-driven) tick.
+ bool mInNormalTick : 1;
+
+ // True if we attempted an extra tick (see CanDoExtraTick) since the last
+ // vsync and thus shouldn't allow another.
+ bool mAttemptedExtraTickSinceLastVsync : 1;
+
+ bool mHasExceededAfterLoadTickPeriod : 1;
+
+ bool mHasStartedTimerAtLeastOnce : 1;
+
+ mozilla::TimeStamp mMostRecentRefresh;
+ mozilla::TimeStamp mTickStart;
+ mozilla::VsyncId mTickVsyncId;
+ mozilla::TimeStamp mTickVsyncTime;
+ mozilla::TimeStamp mNextThrottledFrameRequestTick;
+ mozilla::TimeStamp mNextRecomputeVisibilityTick;
+ mozilla::TimeStamp mBeforeFirstContentfulPaintTimerRunningLimit;
+
+ // separate arrays for each flush type we support
+ ObserverArray mObservers[4];
+ // These observers should NOT be included in HasObservers() since that method
+ // is used to determine whether or not to stop the timer, or restore it when
+ // thawing the refresh driver. On the other hand these observers are intended
+ // to be called when the timer is re-started and should not influence its
+ // starting or stopping.
+ nsTObserverArray<nsATimerAdjustmentObserver*> mTimerAdjustmentObservers;
+ nsTArray<mozilla::layers::CompositionPayload> mCompositionPayloads;
+ RequestTable mRequests;
+ ImageStartTable mStartTable;
+ AutoTArray<nsCOMPtr<nsIRunnable>, 16> mEarlyRunners;
+ VisualViewportResizeEventArray mVisualViewportResizeEvents;
+ ScrollEventArray mScrollEvents;
+ VisualViewportScrollEventArray mVisualViewportScrollEvents;
+
+ // Scroll events on documents that might have events suppressed.
+ ScrollEventArray mDelayedScrollEvents;
+
+ AutoTArray<mozilla::PresShell*, 16> mResizeEventFlushObservers;
+ AutoTArray<mozilla::PresShell*, 16> mDelayedResizeEventFlushObservers;
+ AutoTArray<mozilla::PresShell*, 16> mStyleFlushObservers;
+ AutoTArray<mozilla::PresShell*, 16> mLayoutFlushObservers;
+ // nsTArray on purpose, because we want to be able to swap.
+ nsTArray<Document*> mFrameRequestCallbackDocs;
+ nsTArray<Document*> mThrottledFrameRequestCallbackDocs;
+ nsTArray<RefPtr<Document>> mAutoFocusFlushDocuments;
+ nsTObserverArray<nsAPostRefreshObserver*> mPostRefreshObservers;
+ nsTArray<mozilla::UniquePtr<mozilla::PendingFullscreenEvent>>
+ mPendingFullscreenEvents;
+ AutoTArray<mozilla::AnimationEventDispatcher*, 16>
+ mAnimationEventFlushObservers;
+
+ // nsPresContexts which `NotifyContentfulPaint` have been called,
+ // however the corresponding paint doesn't come from a regular
+ // rendering steps(aka tick).
+ //
+ // For these nsPresContexts, we invoke
+ // `FlushForceNotifyContentfulPaintPresContext` in the next tick
+ // to force notify contentful paint, regardless whether the tick paints
+ // or not.
+ nsTArray<mozilla::WeakPtr<nsPresContext>>
+ mForceNotifyContentfulPaintPresContexts;
+
+ void BeginRefreshingImages(RequestTable& aEntries,
+ mozilla::TimeStamp aDesired);
+
+ friend class mozilla::RefreshDriverTimer;
+
+ static void Shutdown();
+};
+
+#endif /* !defined(nsRefreshDriver_h_) */
diff --git a/layout/base/nsRefreshObservers.cpp b/layout/base/nsRefreshObservers.cpp
new file mode 100644
index 0000000000..44f797c0f1
--- /dev/null
+++ b/layout/base/nsRefreshObservers.cpp
@@ -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/. */
+
+#include "nsRefreshObservers.h"
+#include "nsPresContext.h"
+#include "mozilla/Likely.h"
+
+namespace mozilla {
+
+ManagedPostRefreshObserver::ManagedPostRefreshObserver(nsPresContext* aPc,
+ Action&& aAction)
+ : mPresContext(aPc), mAction(std::move(aAction)) {}
+
+ManagedPostRefreshObserver::ManagedPostRefreshObserver(nsPresContext* aPc)
+ : mPresContext(aPc) {
+ MOZ_ASSERT(mPresContext, "Can't observe without a nsPresContext");
+}
+
+ManagedPostRefreshObserver::~ManagedPostRefreshObserver() = default;
+
+void ManagedPostRefreshObserver::Cancel() {
+ // Caller holds a strong reference, so no need to reference stuff from here.
+ if (mAction) {
+ mAction(true);
+ }
+ mAction = nullptr;
+ mPresContext = nullptr;
+}
+
+void ManagedPostRefreshObserver::DidRefresh() {
+ if (MOZ_UNLIKELY(!mAction)) {
+ return;
+ }
+
+ RefPtr<ManagedPostRefreshObserver> thisObject = this;
+ auto action = std::move(mAction);
+ Unregister unregister = action(false);
+
+ if (unregister == Unregister::Yes) {
+ if (RefPtr<nsPresContext> pc = std::move(mPresContext)) {
+ // We have to null-check mPresContext here because in theory, mAction
+ // could've ended up in `Cancel` being called, which would clear
+ // mPresContext. In that case, we're already unregistered so no need to
+ // do anything.
+ pc->UnregisterManagedPostRefreshObserver(this);
+ }
+ } else {
+ mAction = std::move(action);
+ }
+}
+
+} // namespace mozilla
diff --git a/layout/base/nsRefreshObservers.h b/layout/base/nsRefreshObservers.h
new file mode 100644
index 0000000000..f1516cc4c1
--- /dev/null
+++ b/layout/base/nsRefreshObservers.h
@@ -0,0 +1,113 @@
+/* -*- 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 to notify things that animate before a refresh, at an appropriate
+ * refresh rate. (Perhaps temporary, until replaced by compositor.)
+ */
+
+#ifndef LAYOUT_BASE_NSREFRESHOBSERVERS_H_
+#define LAYOUT_BASE_NSREFRESHOBSERVERS_H_
+
+#include <functional>
+
+#include "mozilla/Attributes.h"
+#include "mozilla/TimeStamp.h"
+#include "nsISupports.h"
+
+class nsPresContext;
+
+namespace mozilla {
+class AnimationEventDispatcher;
+class PendingFullscreenEvent;
+class PresShell;
+class RefreshDriverTimer;
+} // namespace mozilla
+
+/**
+ * An abstract base class to be implemented by callers wanting to be
+ * notified at refresh times. When nothing needs to be painted, callers
+ * may not be notified.
+ */
+class nsARefreshObserver {
+ public:
+ // AddRef and Release signatures that match nsISupports. Implementors
+ // must implement reference counting, and those that do implement
+ // nsISupports will already have methods with the correct signature.
+ //
+ // The refresh driver does NOT hold references to refresh observers
+ // except while it is notifying them.
+ NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
+ MOZ_CAN_RUN_SCRIPT virtual void WillRefresh(mozilla::TimeStamp aTime) = 0;
+
+#ifdef DEBUG
+ // Count of the active registrations we currently have with nsRefreshDrivers.
+ // This is incremented in nsRefreshDriver::AddRefreshObserver, decremented in
+ // nsRefreshDriver::RemoveRefreshObserver. No other code should modify this.
+ // We expect this to be 0 when we're destructed.
+ int32_t mRegistrationCount = 0;
+
+ // Virtual destructor to enforce the mRegistrationCount invariant.
+ virtual ~nsARefreshObserver() {
+ MOZ_ASSERT(mRegistrationCount == 0,
+ "Refresh observer AddRefreshObserver/RemoveRefreshObserver "
+ "calls should have balanced out to zero");
+ }
+#endif // DEBUG
+};
+
+/**
+ * An abstract base class to be implemented by callers wanting to be notified
+ * when the observing refresh driver updated mMostRecentRefresh due to active
+ * timer changes. Callers must ensure an observer is removed before it is
+ * destroyed.
+ */
+class nsATimerAdjustmentObserver {
+ public:
+ virtual void NotifyTimerAdjusted(mozilla::TimeStamp aTime) = 0;
+};
+
+/**
+ * An abstract base class to be implemented by callers wanting to be notified
+ * that a refresh has occurred. Callers must ensure an observer is removed
+ * before it is destroyed.
+ */
+class nsAPostRefreshObserver {
+ public:
+ virtual void DidRefresh() = 0;
+};
+
+namespace mozilla {
+
+/**
+ * A wrapper for nsAPostRefreshObserver that's refcounted and might unregister
+ * itself after firing.
+ *
+ * Note, while the observer unregisters itself, the registering still needs to
+ * be done by the caller.
+ */
+class ManagedPostRefreshObserver : public nsAPostRefreshObserver {
+ public:
+ // Whether the post-refresh observer should be unregistered after it has
+ // fired.
+ enum class Unregister : bool { No, Yes };
+ using Action = std::function<Unregister(bool aWasCanceled)>;
+ NS_INLINE_DECL_REFCOUNTING(ManagedPostRefreshObserver)
+ ManagedPostRefreshObserver(nsPresContext*, Action&&);
+ explicit ManagedPostRefreshObserver(nsPresContext*);
+ void DidRefresh() override;
+ void Cancel();
+
+ protected:
+ virtual ~ManagedPostRefreshObserver();
+ RefPtr<nsPresContext> mPresContext;
+ Action mAction;
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/layout/base/nsStyleChangeList.cpp b/layout/base/nsStyleChangeList.cpp
new file mode 100644
index 0000000000..d41ee99195
--- /dev/null
+++ b/layout/base/nsStyleChangeList.cpp
@@ -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/. */
+
+/*
+ * a list of the recomputation that needs to be done in response to a
+ * style change
+ */
+
+#include "nsStyleChangeList.h"
+
+#include "mozilla/dom/ElementInlines.h"
+
+#include "nsCSSFrameConstructor.h"
+#include "nsIContent.h"
+#include "nsIFrame.h"
+
+void nsStyleChangeList::AppendChange(nsIFrame* aFrame, nsIContent* aContent,
+ nsChangeHint aHint) {
+ MOZ_ASSERT(aFrame || (aHint & nsChangeHint_ReconstructFrame),
+ "must have frame");
+ MOZ_ASSERT(aHint, "No hint to process?");
+ MOZ_ASSERT(!(aHint & nsChangeHint_NeutralChange),
+ "Neutral changes do not need extra processing, "
+ "and should be stripped out");
+ MOZ_ASSERT(aContent || !(aHint & nsChangeHint_ReconstructFrame),
+ "must have content");
+ // XXXbz we should make this take Element instead of nsIContent
+ MOZ_ASSERT(
+ !aContent || aContent->IsElement() ||
+ // display:contents elements posts the changes for their children:
+ (aFrame && aContent->GetFlattenedTreeParentElementForStyle() &&
+ Servo_Element_IsDisplayContents(
+ aContent->GetFlattenedTreeParentElementForStyle())) ||
+ (aContent->IsText() && aContent->HasFlag(NODE_NEEDS_FRAME) &&
+ aHint & nsChangeHint_ReconstructFrame),
+ "Shouldn't be trying to restyle non-elements directly, "
+ "except if it's a display:contents child or a text node "
+ "doing lazy frame construction");
+ MOZ_ASSERT(!(aHint & nsChangeHint_AllReflowHints) ||
+ (aHint & nsChangeHint_NeedReflow),
+ "Reflow hint bits set without actually asking for a reflow");
+
+ if (aHint & nsChangeHint_ReconstructFrame) {
+ // If Servo fires reconstruct at a node, it is the only change hint fired at
+ // that node.
+
+ // Note: Because we check whether |aHint| is a reconstruct above (which is
+ // necessary to avoid debug test timeouts on certain crashtests), this check
+ // will not find bugs where we add a non-reconstruct hint for an element
+ // after adding a reconstruct. This is ok though, since
+ // ProcessRestyledFrames will handle that case via mDestroyedFrames.
+#ifdef DEBUG
+ for (size_t i = 0; i < Length(); ++i) {
+ MOZ_ASSERT(aContent != (*this)[i].mContent ||
+ !((*this)[i].mHint & nsChangeHint_ReconstructFrame),
+ "Should not append a non-ReconstructFrame hint after \
+ appending a ReconstructFrame hint for the same \
+ content.");
+ }
+#endif
+ }
+
+ if (!IsEmpty() && aFrame && aFrame == LastElement().mFrame) {
+ LastElement().mHint |= aHint;
+ return;
+ }
+
+ AppendElement(nsStyleChangeData{aFrame, aContent, aHint});
+}
diff --git a/layout/base/nsStyleChangeList.h b/layout/base/nsStyleChangeList.h
new file mode 100644
index 0000000000..9dd80509dd
--- /dev/null
+++ b/layout/base/nsStyleChangeList.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/. */
+
+/*
+ * a list of the recomputation that needs to be done in response to a
+ * style change
+ */
+
+#ifndef nsStyleChangeList_h___
+#define nsStyleChangeList_h___
+
+#include "mozilla/Attributes.h"
+
+#include "nsChangeHint.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+
+class nsIFrame;
+class nsIContent;
+
+struct nsStyleChangeData {
+ nsIFrame* mFrame; // weak
+ nsCOMPtr<nsIContent> mContent;
+ nsChangeHint mHint;
+};
+
+class nsStyleChangeList : private AutoTArray<nsStyleChangeData, 10> {
+ typedef AutoTArray<nsStyleChangeData, 10> base_type;
+ nsStyleChangeList(const nsStyleChangeList&) = delete;
+
+ public:
+ using base_type::begin;
+ using base_type::Clear;
+ using base_type::end;
+ using base_type::IsEmpty;
+ using base_type::Length;
+ using base_type::operator[];
+
+ MOZ_COUNTED_DEFAULT_CTOR(nsStyleChangeList)
+ MOZ_COUNTED_DTOR(nsStyleChangeList)
+ void AppendChange(nsIFrame* aFrame, nsIContent* aContent, nsChangeHint aHint);
+
+ // Starting from the end of the list, removes all changes until the list is
+ // empty or an element with |mContent != aContent| is found.
+ void PopChangesForContent(nsIContent* aContent) {
+ while (!IsEmpty() && LastElement().mContent == aContent) {
+ RemoveLastElement();
+ }
+ }
+};
+
+#endif /* nsStyleChangeList_h___ */
diff --git a/layout/base/nsStyleSheetService.cpp b/layout/base/nsStyleSheetService.cpp
new file mode 100644
index 0000000000..061e03fd9d
--- /dev/null
+++ b/layout/base/nsStyleSheetService.cpp
@@ -0,0 +1,337 @@
+/* -*- 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 interface for managing user and user-agent style sheets */
+
+#include "nsStyleSheetService.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PreloadedStyleSheet.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/PresShellInlines.h"
+#include "mozilla/StyleSheet.h"
+#include "mozilla/StyleSheetInlines.h"
+#include "mozilla/Unused.h"
+#include "mozilla/css/Loader.h"
+#include "mozilla/dom/ContentParent.h"
+#include "mozilla/dom/Promise.h"
+#include "mozilla/ipc/URIUtils.h"
+#include "nsIURI.h"
+#include "nsCOMPtr.h"
+#include "nsISupportsPrimitives.h"
+#include "nsISimpleEnumerator.h"
+#include "nsNetUtil.h"
+#include "nsIConsoleService.h"
+#include "nsLayoutStatics.h"
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+
+nsStyleSheetService* nsStyleSheetService::gInstance = nullptr;
+
+nsStyleSheetService::nsStyleSheetService() {
+ static_assert(0 == AGENT_SHEET && 1 == USER_SHEET && 2 == AUTHOR_SHEET,
+ "Convention for Style Sheet");
+ NS_ASSERTION(!gInstance,
+ "Someone is using CreateInstance instead of GetService");
+ if (!gInstance) {
+ gInstance = this;
+ }
+ nsLayoutStatics::AddRef();
+}
+
+nsStyleSheetService::~nsStyleSheetService() {
+ UnregisterWeakMemoryReporter(this);
+
+ if (gInstance == this) {
+ gInstance = nullptr;
+ }
+ nsLayoutStatics::Release();
+}
+
+NS_IMPL_ISUPPORTS(nsStyleSheetService, nsIStyleSheetService, nsIMemoryReporter)
+
+static bool SheetHasURI(StyleSheet* aSheet, nsIURI* aSheetURI) {
+ MOZ_ASSERT(aSheetURI);
+
+ bool result;
+ nsIURI* uri = aSheet->GetSheetURI();
+ return uri && NS_SUCCEEDED(uri->Equals(aSheetURI, &result)) && result;
+}
+
+int32_t nsStyleSheetService::FindSheetByURI(uint32_t aSheetType,
+ nsIURI* aSheetURI) {
+ SheetArray& sheets = mSheets[aSheetType];
+ for (int32_t i = sheets.Length() - 1; i >= 0; i--) {
+ if (SheetHasURI(sheets[i], aSheetURI)) {
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+nsresult nsStyleSheetService::Init() {
+ // Child processes get their style sheets from the ContentParent.
+ if (XRE_IsContentProcess()) {
+ return NS_OK;
+ }
+
+ RegisterWeakMemoryReporter(this);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::LoadAndRegisterSheet(nsIURI* aSheetURI,
+ uint32_t aSheetType) {
+ // Warn developers if their stylesheet URL has a #ref at the end.
+ // Stylesheet URIs don't benefit from having a #ref suffix -- and if the
+ // sheet is a data URI, someone might've created this #ref by accident (and
+ // truncated their data-URI stylesheet) by using an unescaped # character in
+ // a #RRGGBB color or #foo() ID-selector in their data-URI representation.
+ bool hasRef;
+ nsresult rv = aSheetURI->GetHasRef(&hasRef);
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (aSheetURI && hasRef) {
+ nsCOMPtr<nsIConsoleService> consoleService =
+ do_GetService(NS_CONSOLESERVICE_CONTRACTID);
+ NS_WARNING_ASSERTION(consoleService, "Failed to get console service!");
+ if (consoleService) {
+ const char16_t* message =
+ u"nsStyleSheetService::LoadAndRegisterSheet: "
+ u"URI contains unescaped hash character, which might be truncating "
+ u"the sheet, if it's a data URI.";
+ consoleService->LogStringMessage(message);
+ }
+ }
+
+ rv = LoadAndRegisterSheetInternal(aSheetURI, aSheetType);
+ if (NS_SUCCEEDED(rv)) {
+ // Hold on to a copy of the registered PresShells.
+ for (PresShell* presShell : mPresShells.Clone()) {
+ StyleSheet* sheet = mSheets[aSheetType].LastElement();
+ presShell->NotifyStyleSheetServiceSheetAdded(sheet, aSheetType);
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsTArray<dom::ContentParent*> children;
+ dom::ContentParent::GetAll(children);
+
+ if (children.IsEmpty()) {
+ return rv;
+ }
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendLoadAndRegisterSheet(aSheetURI, aSheetType);
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult nsStyleSheetService::LoadAndRegisterSheetInternal(
+ nsIURI* aSheetURI, uint32_t aSheetType) {
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ css::SheetParsingMode parsingMode;
+ switch (aSheetType) {
+ case AGENT_SHEET:
+ parsingMode = css::eAgentSheetFeatures;
+ break;
+
+ case USER_SHEET:
+ parsingMode = css::eUserSheetFeatures;
+ break;
+
+ case AUTHOR_SHEET:
+ parsingMode = css::eAuthorSheetFeatures;
+ break;
+
+ default:
+ NS_WARNING("invalid sheet type argument");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<css::Loader> loader = new css::Loader;
+ auto result = loader->LoadSheetSync(aSheetURI, parsingMode,
+ css::Loader::UseSystemPrincipal::Yes);
+ if (result.isErr()) {
+ return result.unwrapErr();
+ }
+ mSheets[aSheetType].AppendElement(result.unwrap());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::SheetRegistered(nsIURI* sheetURI, uint32_t aSheetType,
+ bool* _retval) {
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET || aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+ NS_ENSURE_ARG_POINTER(sheetURI);
+ MOZ_ASSERT(_retval, "Null out param");
+
+ // Check to see if we have the sheet.
+ *_retval = (FindSheetByURI(aSheetType, sheetURI) >= 0);
+
+ return NS_OK;
+}
+
+static nsresult GetParsingMode(uint32_t aSheetType,
+ css::SheetParsingMode* aParsingMode) {
+ switch (aSheetType) {
+ case nsStyleSheetService::AGENT_SHEET:
+ *aParsingMode = css::eAgentSheetFeatures;
+ return NS_OK;
+
+ case nsStyleSheetService::USER_SHEET:
+ *aParsingMode = css::eUserSheetFeatures;
+ return NS_OK;
+
+ case nsStyleSheetService::AUTHOR_SHEET:
+ *aParsingMode = css::eAuthorSheetFeatures;
+ return NS_OK;
+
+ default:
+ NS_WARNING("invalid sheet type argument");
+ return NS_ERROR_INVALID_ARG;
+ }
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::PreloadSheet(nsIURI* aSheetURI, uint32_t aSheetType,
+ nsIPreloadedStyleSheet** aSheet) {
+ MOZ_ASSERT(aSheet, "Null out param");
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ css::SheetParsingMode parsingMode;
+ nsresult rv = GetParsingMode(aSheetType, &parsingMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ auto sheet = MakeRefPtr<PreloadedStyleSheet>(aSheetURI, parsingMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = sheet->Preload();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ sheet.forget(aSheet);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::PreloadSheetAsync(nsIURI* aSheetURI, uint32_t aSheetType,
+ JSContext* aCx,
+ JS::MutableHandle<JS::Value> aRval) {
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ css::SheetParsingMode parsingMode;
+ nsresult rv = GetParsingMode(aSheetType, &parsingMode);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIGlobalObject> global = xpc::CurrentNativeGlobal(aCx);
+ NS_ENSURE_TRUE(global, NS_ERROR_UNEXPECTED);
+
+ ErrorResult errv;
+ RefPtr<dom::Promise> promise = dom::Promise::Create(global, errv);
+ if (errv.Failed()) {
+ return errv.StealNSResult();
+ }
+
+ auto sheet = MakeRefPtr<PreloadedStyleSheet>(aSheetURI, parsingMode);
+ sheet->PreloadAsync(WrapNotNull(promise));
+
+ if (!ToJSValue(aCx, promise, aRval)) {
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsStyleSheetService::UnregisterSheet(nsIURI* aSheetURI, uint32_t aSheetType) {
+ NS_ENSURE_ARG(aSheetType == AGENT_SHEET || aSheetType == USER_SHEET ||
+ aSheetType == AUTHOR_SHEET);
+ NS_ENSURE_ARG_POINTER(aSheetURI);
+
+ RefPtr<StyleSheet> sheet;
+ int32_t foundIndex = FindSheetByURI(aSheetType, aSheetURI);
+ if (foundIndex >= 0) {
+ sheet = mSheets[aSheetType][foundIndex];
+ mSheets[aSheetType].RemoveElementAt(foundIndex);
+ }
+
+ // Hold on to a copy of the registered PresShells.
+ for (PresShell* presShell : mPresShells.Clone()) {
+ if (presShell->StyleSet()) {
+ if (sheet) {
+ presShell->NotifyStyleSheetServiceSheetRemoved(sheet, aSheetType);
+ }
+ }
+ }
+
+ if (XRE_IsParentProcess()) {
+ nsTArray<dom::ContentParent*> children;
+ dom::ContentParent::GetAll(children);
+
+ if (children.IsEmpty()) {
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < children.Length(); i++) {
+ Unused << children[i]->SendUnregisterSheet(aSheetURI, aSheetType);
+ }
+ }
+
+ return NS_OK;
+}
+
+// static
+nsStyleSheetService* nsStyleSheetService::GetInstance() {
+ static bool first = true;
+ if (first) {
+ // make sure at first call that it's inited
+ nsCOMPtr<nsIStyleSheetService> dummy =
+ do_GetService(NS_STYLESHEETSERVICE_CONTRACTID);
+ first = false;
+ }
+
+ return gInstance;
+}
+
+MOZ_DEFINE_MALLOC_SIZE_OF(StyleSheetServiceMallocSizeOf)
+
+NS_IMETHODIMP
+nsStyleSheetService::CollectReports(nsIHandleReportCallback* aHandleReport,
+ nsISupports* aData, bool aAnonymize) {
+ MOZ_COLLECT_REPORT(
+ "explicit/layout/style-sheet-service", KIND_HEAP, UNITS_BYTES,
+ SizeOfIncludingThis(StyleSheetServiceMallocSizeOf),
+ "Memory used for style sheets held by the style sheet service.");
+
+ return NS_OK;
+}
+
+size_t nsStyleSheetService::SizeOfIncludingThis(
+ mozilla::MallocSizeOf aMallocSizeOf) const {
+ size_t n = aMallocSizeOf(this);
+ for (auto& sheetArray : mSheets) {
+ n += sheetArray.ShallowSizeOfExcludingThis(aMallocSizeOf);
+ for (StyleSheet* sheet : sheetArray) {
+ if (sheet) {
+ n += sheet->SizeOfIncludingThis(aMallocSizeOf);
+ }
+ }
+ }
+ return n;
+}
+
+void nsStyleSheetService::RegisterPresShell(PresShell* aPresShell) {
+ MOZ_ASSERT(!mPresShells.Contains(aPresShell));
+ mPresShells.AppendElement(aPresShell);
+}
+
+void nsStyleSheetService::UnregisterPresShell(PresShell* aPresShell) {
+ MOZ_ASSERT(mPresShells.Contains(aPresShell));
+ mPresShells.RemoveElement(aPresShell);
+}
diff --git a/layout/base/nsStyleSheetService.h b/layout/base/nsStyleSheetService.h
new file mode 100644
index 0000000000..79a2f867d9
--- /dev/null
+++ b/layout/base/nsStyleSheetService.h
@@ -0,0 +1,76 @@
+/* -*- 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 interface for managing user and user-agent style sheets */
+
+#ifndef nsStyleSheetService_h_
+#define nsStyleSheetService_h_
+
+#include "nsIMemoryReporter.h"
+#include "nsIStyleSheetService.h"
+#include "mozilla/Array.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/StyleSheet.h"
+
+class nsISimpleEnumerator;
+
+namespace mozilla {
+class PresShell;
+} // namespace mozilla
+
+#define NS_STYLESHEETSERVICE_CID \
+ { \
+ 0x3b55e72e, 0xab7e, 0x431b, { \
+ 0x89, 0xc0, 0x3b, 0x06, 0xa8, 0xb1, 0x40, 0x16 \
+ } \
+ }
+
+#define NS_STYLESHEETSERVICE_CONTRACTID \
+ "@mozilla.org/content/style-sheet-service;1"
+
+class nsStyleSheetService final : public nsIStyleSheetService,
+ public nsIMemoryReporter {
+ public:
+ typedef nsTArray<RefPtr<mozilla::StyleSheet>> SheetArray;
+
+ nsStyleSheetService();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISTYLESHEETSERVICE
+ NS_DECL_NSIMEMORYREPORTER
+
+ nsresult Init();
+
+ SheetArray* AgentStyleSheets() { return &mSheets[AGENT_SHEET]; }
+ SheetArray* UserStyleSheets() { return &mSheets[USER_SHEET]; }
+ SheetArray* AuthorStyleSheets() { return &mSheets[AUTHOR_SHEET]; }
+
+ void RegisterPresShell(mozilla::PresShell* aPresShell);
+ void UnregisterPresShell(mozilla::PresShell* aPresShell);
+
+ size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
+ static nsStyleSheetService* GetInstance();
+ static nsStyleSheetService* gInstance;
+
+ private:
+ ~nsStyleSheetService();
+
+ int32_t FindSheetByURI(uint32_t aSheetType, nsIURI* aSheetURI);
+
+ // Like LoadAndRegisterSheet, but doesn't notify. If successful, the
+ // new sheet will be the last sheet in mSheets[aSheetType].
+ nsresult LoadAndRegisterSheetInternal(nsIURI* aSheetURI, uint32_t aSheetType);
+
+ mozilla::Array<SheetArray, 3> mSheets;
+
+ // Registered PresShells that will be notified when sheets are added and
+ // removed from the style sheet service.
+ nsTArray<RefPtr<mozilla::PresShell>> mPresShells;
+};
+
+#endif
diff --git a/layout/base/tests/Ahem.ttf b/layout/base/tests/Ahem.ttf
new file mode 100644
index 0000000000..ac81cb0316
--- /dev/null
+++ b/layout/base/tests/Ahem.ttf
Binary files differ
diff --git a/layout/base/tests/accessiblecaret_magnifier.html b/layout/base/tests/accessiblecaret_magnifier.html
new file mode 100644
index 0000000000..855a027725
--- /dev/null
+++ b/layout/base/tests/accessiblecaret_magnifier.html
@@ -0,0 +1,91 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<p id="display">
+ <div id="editable" contenteditable style="width: 400px; font-size: 4em;">foobarbaz</div>
+</p>
+<script>
+const SimpleTest = parent.SimpleTest;
+const is = parent.is;
+const info = parent.info;
+const isnot = parent.isnot;
+const ok = parent.ok;
+
+window.addEventListener("load", runTest);
+
+async function runTest() {
+ let target = document.getElementById("editable");
+ target.focus();
+ let targetRect = target.getBoundingClientRect();
+ let selection = window.getSelection();
+
+ // Select word then show accessible caret
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ ok(!selection.getRangeAt(0).collapsed, "Select word");
+
+ let rangeRect = selection.getRangeAt(0).getBoundingClientRect();
+ let presscaret = 0;
+ let dragcaret = 0;
+ let releasecaret = 0;
+
+ const dragStart = {
+ x: Math.round(rangeRect.left),
+ y: Math.round(rangeRect.bottom + 12)
+ };
+ const dragEnd = {
+ x: Math.round(rangeRect.left + 60),
+ y: Math.round(rangeRect.bottom + 12)
+ };
+ let handler;
+
+ let promise = new Promise(resolve => {
+ handler = function(e) {
+ info("mozcaretstatechanged is fired with " + e.reason);
+ switch (e.reason) {
+ case "presscaret":
+ is(dragStart.x, e.clientX, "dragcaret event has clientX data.");
+ is(dragStart.y, e.clientY, "dragcaret event has clientY data.");
+ presscaret++;
+ break;
+ case "dragcaret":
+ is(dragEnd.x, e.clientX, "dragcaret event has clientX data.");
+ is(dragEnd.y, e.clientY, "dragcaret event has clientY data.");
+ dragcaret++;
+ break;
+ case "releasecaret":
+ releasecaret++;
+ resolve();
+ break;
+ }
+ };
+ });
+
+ SpecialPowers.addChromeEventListener("mozcaretstatechanged", handler, true);
+
+ // Drag accessible caret
+ synthesizeTouchAtPoint(dragStart.x, dragStart.y, { type: "touchstart" });
+ synthesizeTouchAtPoint(dragEnd.x, dragEnd.y, { type: "touchmove" });
+ synthesizeTouchAtPoint(dragEnd.x, dragEnd.y, { type: "touchend" });
+
+ await promise;
+
+ SpecialPowers.removeChromeEventListener("mozcaretstatechanged", handler, true);
+
+ is(presscaret, 1, "presscaret is fired correctly");
+ is(dragcaret, 1, "presscaret is fired correctly");
+ is(releasecaret, 1, "releasecaret is fired correctly");
+
+ let newRangeRect = selection.getRangeAt(0).getBoundingClientRect();
+ isnot(rangeRect.left, newRangeRect.left,
+ "Selected range is changed by dragging accessible caret");
+
+ SimpleTest.finish();
+}
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/border_radius_hit_testing_iframe.html b/layout/base/tests/border_radius_hit_testing_iframe.html
new file mode 100644
index 0000000000..a0f7ba1b92
--- /dev/null
+++ b/layout/base/tests/border_radius_hit_testing_iframe.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML>
+<title>border-radius hit testing</title>
+<style>
+
+ body { margin: 0; }
+
+ #one, #two {
+ border-radius: 100px 60px 40px 120px / 40px 60px 60px 80px;
+ margin-bottom: 100px;
+ }
+
+ #two { overflow: hidden }
+
+ #one, #two > div {
+ height: 200px;
+ background: blue;
+ cursor: progress;
+ }
+
+ #one:hover, #two > div:hover {
+ background: fuchsia;
+ }
+
+</style>
+<body>
+<div id="one"></div>
+<div id="two"><div></div></div>
diff --git a/layout/base/tests/browser.toml b/layout/base/tests/browser.toml
new file mode 100644
index 0000000000..a5b279145f
--- /dev/null
+++ b/layout/base/tests/browser.toml
@@ -0,0 +1,61 @@
+[DEFAULT]
+prefs = [
+ "layout.css.properties-and-values.enabled=true",
+]
+
+["browser_bug617076.js"]
+
+["browser_bug1701027-1.js"]
+support-files = ["helper_bug1701027-1.html"]
+
+["browser_bug1701027-2.js"]
+support-files = ["helper_bug1701027-2.html"]
+
+["browser_bug1757410.js"]
+run-if = [
+ "os == 'mac' && debug",
+ "os == 'win' && processor == 'x86_64' && debug"
+]
+
+["browser_bug1787079.js"]
+run-if = ["os == 'win' && processor == 'x86_64' && debug"]
+
+["browser_bug1791083.js"]
+skip-if = ["!sessionHistoryInParent"]
+
+["browser_css_registered_property.js"]
+
+["browser_disableDialogs_onbeforeunload.js"]
+
+["browser_onbeforeunload_only_after_interaction.js"]
+
+["browser_onbeforeunload_only_after_interaction_in_frame.js"]
+
+["browser_scroll_into_view_in_out_of_process_iframe.js"]
+support-files = [
+ "test_scroll_into_view_in_oopif.html",
+ "scroll_into_view_in_child.html"
+]
+
+["browser_select_popup_position_in_out_of_process_iframe.js"]
+skip-if = [
+ "verify && (os == 'mac')", # bug 1627874
+ "apple_silicon", # Disabled due to bleedover with other tests when run in regular suites; passes in "failures" jobs
+ "os == 'linux' && socketprocess_networking && fission && !debug",
+] # high frequency intermittent
+support-files = [
+ "!/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ "!/browser/base/content/test/forms/head.js"
+]
+
+["browser_stylesheet_change_events.js"]
+support-files = [
+ "file_stylesheet_change_events.html",
+ "stylesheet_change_events.css"
+]
+
+["browser_visual_viewport_iframe.js"]
+support-files = [
+ "test_visual_viewport_in_oopif.html",
+ "visual_viewport_in_child.html"
+]
diff --git a/layout/base/tests/browser_bug1701027-1.js b/layout/base/tests/browser_bug1701027-1.js
new file mode 100644
index 0000000000..7f35bd4f00
--- /dev/null
+++ b/layout/base/tests/browser_bug1701027-1.js
@@ -0,0 +1,136 @@
+/* This test is based on
+ https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js
+*/
+
+// In order for this test to test the original bug we need:
+// 1) At least e10s enabled so that apz is enabled so we can create an
+// nsDisplayAsyncZoom item
+// (the insertion of this item without marking the required frame modified
+// is what causes the bug in the retained display list merging)
+// 2) a root content document, again so that we can create a nsDisplayAsyncZoom
+// item
+// 3) the root content document cannot have a display port to start
+// (if it has a display port then it gets a nsDisplayAsyncZoom, but we need
+// that to be created after the anonymous content we insert into the
+// document)
+// Point 3) requires the root content document to be in the parent process,
+// since if it is in a content process it will get a displayport for being at
+// the root of a process.
+// Creating an in-process root content document I think is not possible in
+// mochitest-plain. mochitest-chrome does not have e10s enabled. So this has to
+// be a mochitest-browser-chrome test.
+
+// Outline of this test:
+// Open a new tab with a pretty simple content file, that is not scrollable
+// Use the anonymous content api to insert into that content doc
+// Send a mouse click over the content doc
+// The click hits fixed pos content.
+// This sets a displayport on the root scroll frame of the content doc.
+// (This is because we call GetAsyncScrollableAncestorFrame in
+// PrepareForSetTargetAPZCNotification
+// https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/util/APZCCallbackHelper.cpp#624
+// which passes the SCROLLABLE_FIXEDPOS_FINDS_ROOT flag
+// https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/base/nsLayoutUtils.cpp#2884
+// so starting from fixed pos content means we always find the root scroll
+// frame, whereas if we started from non-fixed content we'd walk pass the root
+// scroll frame becase it isn't scrollable.)
+// Then we have to be careful not to do anything that causes a full display
+// list rebuild.
+// And finally we change the color of the fixed element which covers the whole
+// viewport which causes us to do a partial display list update including the
+// anonymous content, which hits the assert we are aiming to test.
+
+add_task(async function () {
+ function getChromeURL(filename) {
+ let chromeURL = getRootDirectory(gTestPath) + filename;
+ return chromeURL;
+ }
+
+ // We need this otherwise there is a burst animation on the new tab when it
+ // loads and that somehow scrolls a scroll frame, which makes it active,
+ // which makes the scrolled frame an AGR, which means we have multiple AGRs
+ // (the display port makes the root scroll frame active and an AGR) so we hit
+ // this
+ // https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/painting/RetainedDisplayListBuilder.cpp#1179
+ // and are forced to do a full display list rebuild and that prevents us from
+ // testing the original bug.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.prefersReducedMotion", 1]],
+ });
+
+ const pageUrl = getChromeURL("helper_bug1701027-1.html");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ const [theX, theY] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ const rect = content.document
+ .getElementById("fd")
+ .getBoundingClientRect();
+ const x = content.window.mozInnerScreenX + rect.left + rect.width / 2;
+ const y = content.window.mozInnerScreenY + rect.top + rect.height / 2;
+
+ let doc = SpecialPowers.wrap(content.document);
+ var bq = doc.createElement("blockquote");
+ bq.textContent = "This blockquote text.";
+ var div = doc.createElement("div");
+ div.textContent = " This div text.";
+ bq.appendChild(div);
+ var ac = doc.insertAnonymousContent(bq);
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ return [x, y];
+ }
+ );
+
+ // We intentionally turn off a11y_checks, because the following click
+ // is targeting test content that's not meant to be interactive and
+ // is not expected to be accessible:
+ AccessibilityUtils.setEnv({
+ mustHaveAccessibleRule: false,
+ });
+ EventUtils.synthesizeNativeMouseEvent({
+ type: "click",
+ target: window.document.documentElement,
+ screenX: theX,
+ screenY: theY,
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "blue";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "red";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ ok(true, "didn't crash");
+});
diff --git a/layout/base/tests/browser_bug1701027-2.js b/layout/base/tests/browser_bug1701027-2.js
new file mode 100644
index 0000000000..00e55ca562
--- /dev/null
+++ b/layout/base/tests/browser_bug1701027-2.js
@@ -0,0 +1,126 @@
+/* This test is based on
+ https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/gfx/layers/apz/test/mochitest/browser_test_select_zoom.js
+*/
+
+// In order for this test to test the original bug we need:
+// 1) At least e10s enabled so that apz is enabled so we can create an
+// nsDisplayAsyncZoom item
+// (the insertion of this item without marking the required frame modified
+// is what causes the bug in the retained display list merging)
+// 2) a root content document, again so that we can create a nsDisplayAsyncZoom
+// item
+// 3) the root content document cannot have a display port to start
+// (if it has a display port then it gets a nsDisplayAsyncZoom, but we need
+// that to be created after the anonymous content we insert into the
+// document)
+// Point 3) requires the root content document to be in the parent process,
+// since if it is in a content process it will get a displayport for being at
+// the root of a process.
+// Creating an in-process root content document I think is not possible in
+// mochitest-plain. mochitest-chrome does not have e10s enabled. So this has to
+// be a mochitest-browser-chrome test.
+
+// Outline of this test:
+// Open a new tab with a pretty simple content file, that is not scrollable
+// Use the anonymous content api to insert into that content doc
+// Set a displayport on the root scroll frame of the content doc directly.
+// Then we have to be careful not to do anything that causes a full display
+// list rebuild.
+// And finally we change the color of the fixed element which covers the whole
+// viewport which causes us to do a partial display list update including the
+// anonymous content, which hits the assert we are aiming to test.
+
+add_task(async function () {
+ function getChromeURL(filename) {
+ let chromeURL = getRootDirectory(gTestPath) + filename;
+ return chromeURL;
+ }
+
+ // We need this otherwise there is a burst animation on the new tab when it
+ // loads and that somehow scrolls a scroll frame, which makes it active,
+ // which makes the scrolled frame an AGR, which means we have multiple AGRs
+ // (the display port makes the root scroll frame active and an AGR) so we hit
+ // this
+ // https://searchfox.org/mozilla-central/rev/e082df56bbfeaff0f388e7da9da401ff414df18f/layout/painting/RetainedDisplayListBuilder.cpp#1179
+ // and are forced to do a full display list rebuild and that prevents us from
+ // testing the original bug.
+ await SpecialPowers.pushPrefEnv({
+ set: [["ui.prefersReducedMotion", 1]],
+ });
+
+ const pageUrl = getChromeURL("helper_bug1701027-2.html");
+ let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ const [theX, theY] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ const rect = content.document
+ .getElementById("fd")
+ .getBoundingClientRect();
+ const x = content.window.mozInnerScreenX + rect.left + rect.width / 2;
+ const y = content.window.mozInnerScreenY + rect.top + rect.height / 2;
+
+ let doc = SpecialPowers.wrap(content.document);
+ var bq = doc.createElement("blockquote");
+ bq.textContent = "This blockquote text.";
+ var div = doc.createElement("div");
+ div.textContent = " This div text.";
+ bq.appendChild(div);
+ var ac = doc.insertAnonymousContent(bq);
+ content.document.body.offsetWidth;
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ content.window.windowUtils.setDisplayPortMarginsForElement(
+ 0,
+ 0,
+ 0,
+ 0,
+ doc.documentElement,
+ 1
+ );
+ content.window.windowUtils.setDisplayPortBaseForElement(
+ 0,
+ 0,
+ 100,
+ 100,
+ doc.documentElement
+ );
+
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+
+ return [x, y];
+ }
+ );
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "blue";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ content.document.getElementById("fd").style.backgroundColor = "red";
+ });
+
+ await new Promise(resolve => setTimeout(resolve, 0));
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async () => {
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ await new Promise(r => content.window.requestAnimationFrame(r));
+ });
+
+ BrowserTestUtils.removeTab(tab);
+
+ ok(true, "didn't crash");
+});
diff --git a/layout/base/tests/browser_bug1757410.js b/layout/base/tests/browser_bug1757410.js
new file mode 100644
index 0000000000..59c740e5a8
--- /dev/null
+++ b/layout/base/tests/browser_bug1757410.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PAGECONTENT =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<style>" +
+ "html { " +
+ " height: 120vh;" +
+ " overflow-y: scroll;" +
+ "}" +
+ "</style>" +
+ "</html>";
+
+const pageUrl = "data:text/html," + encodeURIComponent(PAGECONTENT);
+
+add_task(async function test() {
+ if (window.devicePixelRatio == 1) {
+ ok(
+ true,
+ "Skip this test since this test is supposed to run on HiDPI mode, " +
+ "the devixePixelRato on this machine is " +
+ window.devicePixelRatio
+ );
+ return;
+ }
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ // Scroll the content a bit.
+ const originalScrollPosition = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ content.document.scrollingElement.scrollTop = 100;
+ return content.document.scrollingElement.scrollTop;
+ }
+ );
+
+ // Disabling HiDPI mode and check the scroll position.
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(false);
+ // Make sure we restore even if this test failed.
+ registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreHiDPIMode();
+ });
+
+ const scrollPosition = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ async () => {
+ return content.document.scrollingElement.scrollTop;
+ }
+ );
+ is(
+ originalScrollPosition,
+ scrollPosition,
+ "The scroll position should be kept"
+ );
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/layout/base/tests/browser_bug1787079.js b/layout/base/tests/browser_bug1787079.js
new file mode 100644
index 0000000000..2161b1f3dc
--- /dev/null
+++ b/layout/base/tests/browser_bug1787079.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const PAGECONTENT =
+ "<!DOCTYPE html>" +
+ "<html>" +
+ "<style>" +
+ "html { height: 100vh; }" +
+ "</style>" +
+ "</html>";
+
+const pageUrl = "data:text/html," + encodeURIComponent(PAGECONTENT);
+
+add_task(async function test() {
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(true);
+ registerCleanupFunction(() => {
+ SpecialPowers.DOMWindowUtils.restoreHiDPIMode();
+ });
+
+ const tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageUrl);
+
+ // Enter fullscreen.
+ let fullscreenChangePromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.documentElement.requestFullscreen();
+ });
+ await fullscreenChangePromise;
+
+ let [originalInnerWidth, originalInnerHeight] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [content.window.innerWidth, content.window.innerHeight];
+ }
+ );
+
+ // Then change the DPI.
+ let originalPixelRatio = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return content.window.devicePixelRatio;
+ }
+ );
+ let dpiChangedPromise = TestUtils.waitForCondition(async () => {
+ let pixelRatio = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ return content.window.devicePixelRatio;
+ });
+ return pixelRatio != originalPixelRatio;
+ }, "Make sure the DPI changed");
+ SpecialPowers.DOMWindowUtils.setHiDPIMode(false);
+ await dpiChangedPromise;
+
+ let [innerWidth, innerHeight] = await SpecialPowers.spawn(
+ tab.linkedBrowser,
+ [],
+ () => {
+ return [content.window.innerWidth, content.window.innerHeight];
+ }
+ );
+
+ Assert.less(
+ originalInnerWidth,
+ innerWidth,
+ "window.innerWidth on a lower DPI should be greater than the original"
+ );
+ Assert.less(
+ originalInnerHeight,
+ innerHeight,
+ "window.innerHeight on a lower DPI should be greater than the original"
+ );
+
+ fullscreenChangePromise = BrowserTestUtils.waitForContentEvent(
+ tab.linkedBrowser,
+ "fullscreenchange"
+ );
+ await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ content.document.exitFullscreen();
+ });
+ await fullscreenChangePromise;
+
+ BrowserTestUtils.removeTab(tab);
+});
diff --git a/layout/base/tests/browser_bug1791083.js b/layout/base/tests/browser_bug1791083.js
new file mode 100644
index 0000000000..e68a723c4e
--- /dev/null
+++ b/layout/base/tests/browser_bug1791083.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const URL =
+ "data:text/html," +
+ "<style> a:hover {background-color: black}</style>" +
+ "<body style='width:100px;height:100px'>" +
+ "<a href='http://www.example.com'>Click Me</a>" +
+ "</body>";
+
+function isAnchorHovered(win) {
+ return SpecialPowers.spawn(
+ win.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ return a.matches(":hover");
+ }
+ );
+}
+
+add_task(async function test() {
+ let newWin = await BrowserTestUtils.openNewBrowserWindow();
+
+ // This bug is only reproducible if the cursor is out of the viewport, so
+ // we resize the window to ensure the cursor is out of the viewport.
+
+ // SynthesizeMouse isn't sufficient because it only synthesizes
+ // mouse events without actually moving the cursor permanently to a
+ // new location.
+ newWin.resizeTo(50, 50);
+
+ BrowserTestUtils.startLoadingURIString(newWin.gBrowser.selectedBrowser, URL);
+ await BrowserTestUtils.browserLoaded(newWin.gBrowser.selectedBrowser);
+
+ await SpecialPowers.spawn(
+ newWin.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ await EventUtils.synthesizeMouseAtCenter(
+ a,
+ { type: "mousemove" },
+ content
+ );
+ }
+ );
+
+ // We've hovered the anchor element.
+ let anchorHovered = await isAnchorHovered(newWin);
+ ok(anchorHovered, "Anchor should be hovered");
+
+ let locationChange = BrowserTestUtils.waitForLocationChange(newWin.gBrowser);
+
+ // Click the anchor to navigate away
+ await SpecialPowers.spawn(
+ newWin.gBrowser.selectedBrowser,
+ [],
+ async function () {
+ const a = content.document.querySelector("a");
+ await EventUtils.synthesizeMouseAtCenter(
+ a,
+ { type: "mousedown" },
+ content
+ );
+ await EventUtils.synthesizeMouseAtCenter(a, { type: "mouseup" }, content);
+ }
+ );
+ await locationChange;
+
+ // Navigate back to the previous page which has the anchor
+ locationChange = BrowserTestUtils.waitForLocationChange(newWin.gBrowser);
+ newWin.gBrowser.selectedBrowser.goBack();
+ await locationChange;
+
+ // Hover state should be cleared upon page caching.
+ anchorHovered = await isAnchorHovered(newWin);
+ ok(!anchorHovered, "Anchor should not be hovered");
+
+ BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/layout/base/tests/browser_bug617076.js b/layout/base/tests/browser_bug617076.js
new file mode 100644
index 0000000000..c76cbd41d3
--- /dev/null
+++ b/layout/base/tests/browser_bug617076.js
@@ -0,0 +1,71 @@
+/**
+ * 1. load about:addons in a new tab and select that tab
+ * 2. insert a button with tooltiptext
+ * 3. create a new blank tab and select that tab
+ * 4. select the about:addons tab and hover the inserted button
+ * 5. remove the about:addons tab
+ * 6. remove the blank tab
+ *
+ * the test succeeds if it doesn't trigger any assertions
+ */
+
+add_task(async function test() {
+ // Open the test tab
+ let testTab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:addons"
+ );
+
+ // insert button into test page content
+ await SpecialPowers.spawn(gBrowser.selectedBrowser, [], async function () {
+ let doc = content.document;
+ let e = doc.createXULElement("button");
+ e.setAttribute("label", "hello");
+ e.setAttribute("tooltiptext", "world");
+ e.setAttribute("id", "test-button");
+ doc.documentElement.insertBefore(e, doc.documentElement.firstChild);
+ });
+
+ // open a second tab and select it
+ let tab2 = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ "about:blank",
+ true
+ );
+ gBrowser.selectedTab = tab2;
+
+ // Select the testTab then perform mouse events on inserted button
+ gBrowser.selectedTab = testTab;
+ let browser = gBrowser.selectedBrowser;
+ EventUtils.disableNonTestMouseEvents(true);
+ try {
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 1,
+ 1,
+ { type: "mouseover" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 2,
+ 6,
+ { type: "mousemove" },
+ browser
+ );
+ await BrowserTestUtils.synthesizeMouse(
+ "#test-button",
+ 2,
+ 4,
+ { type: "mousemove" },
+ browser
+ );
+ } finally {
+ EventUtils.disableNonTestMouseEvents(false);
+ }
+
+ // cleanup
+ BrowserTestUtils.removeTab(testTab);
+ BrowserTestUtils.removeTab(tab2);
+ ok(true, "pass if no assertions");
+});
diff --git a/layout/base/tests/browser_css_registered_property.js b/layout/base/tests/browser_css_registered_property.js
new file mode 100644
index 0000000000..3a8999d87c
--- /dev/null
+++ b/layout/base/tests/browser_css_registered_property.js
@@ -0,0 +1,93 @@
+"use strict";
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "data:text/html,<meta charset=utf8>" },
+ async function (browser) {
+ await SpecialPowers.spawn(browser, [], testRegisterProperty);
+ }
+ );
+});
+
+// This function runs entirely in the content process. It doesn't have access
+// any free variables in this file.
+async function testRegisterProperty() {
+ let doc = content.document;
+ doc.styleSheetChangeEventsEnabled = true;
+
+ const EVENT_NAME = "csscustompropertyregistered";
+
+ const unexpectedContentEvent = event =>
+ ok(false, "Received a " + event.type + " event on content");
+ doc.addEventListener(EVENT_NAME, unexpectedContentEvent);
+ doc.defaultView.addEventListener(EVENT_NAME, unexpectedContentEvent);
+ doc.addEventListener(EVENT_NAME, unexpectedContentEvent);
+ doc.defaultView.addEventListener(EVENT_NAME, unexpectedContentEvent);
+
+ function waitForCssCustomPropertyRegistered() {
+ return ContentTaskUtils.waitForEvent(
+ docShell.chromeEventHandler,
+ EVENT_NAME,
+ true
+ );
+ }
+
+ function checkCssCustomPropertyRegisteredEvent(
+ event,
+ expectedPropertyDefinition
+ ) {
+ is(event.type, EVENT_NAME, "event.type has expected value");
+ is(event.target, doc, "event targets correct document");
+ Assert.deepEqual(event.propertyDefinition, expectedPropertyDefinition);
+ }
+
+ let onCustomPropertyRegistered, evt;
+
+ info("Register property and wait for event");
+ onCustomPropertyRegistered = waitForCssCustomPropertyRegistered();
+ content.CSS.registerProperty({ name: "--a", syntax: "*", inherits: false });
+ evt = await onCustomPropertyRegistered;
+ ok(true, `Received ${EVENT_NAME} event after registering --a`);
+ checkCssCustomPropertyRegisteredEvent(evt, {
+ name: "--a",
+ syntax: "*",
+ inherits: false,
+ initialValue: null,
+ fromJS: true,
+ });
+
+ info("Register another property and wait for a new event");
+ onCustomPropertyRegistered = waitForCssCustomPropertyRegistered();
+ content.CSS.registerProperty({
+ name: "--b",
+ syntax: "<color>",
+ inherits: true,
+ initialValue: "tomato",
+ });
+ evt = await onCustomPropertyRegistered;
+ ok(true, `Received ${EVENT_NAME} event after registering --b`);
+ checkCssCustomPropertyRegisteredEvent(evt, {
+ name: "--b",
+ syntax: "<color>",
+ inherits: true,
+ initialValue: "tomato",
+ fromJS: true,
+ });
+
+ info("Register existing property and assert that we don't get an event");
+ onCustomPropertyRegistered = waitForCssCustomPropertyRegistered();
+ const timeout = new Promise(resolve =>
+ content.setTimeout(() => resolve("TIMEOUT"), 500)
+ );
+ try {
+ content.CSS.registerProperty({ name: "--b", syntax: "*", inherits: false });
+ } catch (e) {
+ ok(true, "CSS.registerProperty threw");
+ }
+ const res = await Promise.race([onCustomPropertyRegistered, timeout]);
+ is(
+ res,
+ "TIMEOUT",
+ `Did not receive ${EVENT_NAME} event when registration failed`
+ );
+}
diff --git a/layout/base/tests/browser_disableDialogs_onbeforeunload.js b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
new file mode 100644
index 0000000000..f060d1db51
--- /dev/null
+++ b/layout/base/tests/browser_disableDialogs_onbeforeunload.js
@@ -0,0 +1,64 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", false]],
+});
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent("<script>(" + pageScript.toSource() + ")();</script>");
+
+add_task(async function enableDialogs() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function disableDialogs() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(enableDialogs) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.startLoadingURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+ // Load the content script in the frame.
+ let methodName = enableDialogs ? "enableDialogs" : "disableDialogs";
+ await SpecialPowers.spawn(browser, [methodName], async function (name) {
+ content.windowUtils[name]();
+ });
+ // And then navigate away.
+ BrowserTestUtils.startLoadingURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
new file mode 100644
index 0000000000..b0a577335b
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction.js
@@ -0,0 +1,75 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [["dom.require_user_interaction_for_beforeunload", true]],
+});
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent("<script>(" + pageScript.toSource() + ")();</script>");
+
+add_task(async function doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.startLoadingURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ if (shouldClick) {
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, browser);
+ }
+ let hasInteractedWith = await SpecialPowers.spawn(
+ browser,
+ [""],
+ function () {
+ return content.document.userHasInteracted;
+ }
+ );
+ is(
+ shouldClick,
+ hasInteractedWith,
+ "Click should update document interactivity state"
+ );
+ // And then navigate away.
+ BrowserTestUtils.startLoadingURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
new file mode 100644
index 0000000000..bf9ab67254
--- /dev/null
+++ b/layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js
@@ -0,0 +1,96 @@
+const { PromptTestUtils } = ChromeUtils.importESModule(
+ "resource://testing-common/PromptTestUtils.sys.mjs"
+);
+
+function pageScript() {
+ window.addEventListener(
+ "beforeunload",
+ function (event) {
+ var str = "Some text that causes the beforeunload dialog to be shown";
+ event.returnValue = str;
+ return str;
+ },
+ true
+ );
+}
+
+SpecialPowers.pushPrefEnv({
+ set: [
+ ["dom.require_user_interaction_for_beforeunload", true],
+ ["security.allow_eval_with_system_principal", true],
+ ],
+});
+
+const FRAME_URL =
+ "data:text/html," + encodeURIComponent("<body>Just a frame</body>");
+
+const PAGE_URL =
+ "data:text/html," +
+ encodeURIComponent(
+ "<iframe src='" +
+ FRAME_URL +
+ "'></iframe><script>(" +
+ pageScript.toSource() +
+ ")();</script>"
+ );
+
+add_task(async function doClick() {
+ // The onbeforeunload dialog should appear.
+ let dialogPromise = PromptTestUtils.waitForPrompt(null, {
+ modalType: Services.prompt.MODAL_TYPE_CONTENT,
+ promptType: "confirmEx",
+ });
+
+ let openPagePromise = openPage(true);
+ let dialog = await dialogPromise;
+ Assert.ok(true, "Showed the beforeunload dialog.");
+
+ await PromptTestUtils.handlePrompt(dialog, { buttonNumClick: 0 });
+ await openPagePromise;
+});
+
+add_task(async function noClick() {
+ // The onbeforeunload dialog should NOT appear.
+ await openPage(false);
+ info("If we time out here, then the dialog was shown...");
+});
+
+async function openPage(shouldClick) {
+ // Open about:blank in a new tab.
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: "about:blank" },
+ async function (browser) {
+ // Load the page.
+ BrowserTestUtils.startLoadingURIString(browser, PAGE_URL);
+ await BrowserTestUtils.browserLoaded(browser);
+
+ let frameBC = browser.browsingContext.children[0];
+ if (shouldClick) {
+ await BrowserTestUtils.synthesizeMouse("body", 2, 2, {}, frameBC);
+ }
+ let hasInteractedWith = await SpecialPowers.spawn(
+ frameBC,
+ [],
+ function () {
+ return [
+ content.document.userHasInteracted,
+ content.document.userHasInteracted,
+ ];
+ }
+ );
+ is(
+ shouldClick,
+ hasInteractedWith[0],
+ "Click should update parent interactivity state"
+ );
+ is(
+ shouldClick,
+ hasInteractedWith[1],
+ "Click should update frame interactivity state"
+ );
+ // And then navigate away.
+ BrowserTestUtils.startLoadingURIString(browser, "http://example.com/");
+ await BrowserTestUtils.browserLoaded(browser);
+ }
+ );
+}
diff --git a/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js b/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..07369feb4d
--- /dev/null
+++ b/layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js
@@ -0,0 +1,51 @@
+"use strict";
+
+add_task(async () => {
+ function httpURL(filename, host = "https://example.com/") {
+ let root = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ host
+ );
+ return root + filename;
+ }
+
+ const fissionWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+ const url = httpURL(
+ "test_scroll_into_view_in_oopif.html",
+ "http://mochi.test:8888/"
+ );
+ const crossOriginIframeUrl = httpURL("scroll_into_view_in_child.html");
+
+ try {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: fissionWindow.gBrowser, url },
+ async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [crossOriginIframeUrl],
+ async iframeUrl => {
+ const iframe = content.document.getElementById("iframe");
+ iframe.setAttribute("src", iframeUrl);
+
+ // Wait for a scroll event since scrollIntoView for cross origin documents is
+ // asyncronously processed.
+ const scroller = content.document.getElementById("scroller");
+ await new Promise(resolve => {
+ scroller.addEventListener("scroll", resolve, { once: true });
+ });
+
+ Assert.greater(
+ scroller.scrollTop,
+ 0,
+ "scrollIntoView works in a cross origin iframe"
+ );
+ }
+ );
+ }
+ );
+ } finally {
+ await BrowserTestUtils.closeWindow(fissionWindow);
+ }
+});
diff --git a/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js b/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js
new file mode 100644
index 0000000000..2472a658db
--- /dev/null
+++ b/layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js
@@ -0,0 +1,125 @@
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/gfx/layers/apz/test/mochitest/apz_test_native_event_utils.js",
+ this
+);
+
+Services.scriptloader.loadSubScript(
+ "chrome://mochitests/content/browser/browser/base/content/test/forms/head.js",
+ this
+);
+
+const PAGECONTENT_TRANSLATED =
+ "<html><body>" +
+ "<div id='div'>" +
+ "<iframe id='frame' width='320' height='295' style='margin: 100px;'" +
+ " src='https://example.com/document-builder.sjs?html=<html><select id=select><option>he he he</option><option>boo boo</option><option>baz baz</option></select></html>'" +
+ "</iframe>" +
+ "</div></body></html>";
+
+function openSelectPopup(x, y, win) {
+ const popupShownPromise = BrowserTestUtils.waitForSelectPopupShown(win);
+ EventUtils.synthesizeNativeMouseEvent({
+ type: "click",
+ target: win.document.documentElement,
+ screenX: x,
+ screenY: y,
+ });
+ return popupShownPromise;
+}
+
+add_task(async function () {
+ const pageUrl = "data:text/html," + escape(PAGECONTENT_TRANSLATED);
+
+ const newWin = await BrowserTestUtils.openNewBrowserWindow({ fission: true });
+
+ const browserLoadedPromise = BrowserTestUtils.browserLoaded(
+ newWin.gBrowser.selectedBrowser,
+ true /* includeSubFrames */
+ );
+ BrowserTestUtils.startLoadingURIString(
+ newWin.gBrowser.selectedBrowser,
+ pageUrl
+ );
+ await browserLoadedPromise;
+
+ newWin.gBrowser.selectedBrowser.focus();
+
+ const tab = newWin.gBrowser.selectedTab;
+
+ // We need to explicitly call Element.focus() since dataURL is treated as
+ // cross-origin, thus autofocus doesn't work there.
+ const iframeBC = await SpecialPowers.spawn(tab.linkedBrowser, [], () => {
+ return content.document.querySelector("iframe").browsingContext;
+ });
+
+ const [iframeBorderLeft, iframeBorderTop, iframeX, iframeY] =
+ await SpecialPowers.spawn(tab.linkedBrowser, [], async function () {
+ await SpecialPowers.contentTransformsReceived(content);
+ const iframe = content.document.querySelector("iframe");
+ const rect = iframe.getBoundingClientRect();
+ const x = content.window.mozInnerScreenX + rect.left;
+ const y = content.window.mozInnerScreenY + rect.top;
+ const cs = content.window.getComputedStyle(iframe);
+ return [parseInt(cs.borderLeftWidth), parseInt(cs.borderTopWidth), x, y];
+ });
+
+ const selectRect = await SpecialPowers.spawn(iframeBC, [], async function () {
+ await SpecialPowers.contentTransformsReceived(content);
+ const input = content.document.getElementById("select");
+ const focusPromise = new Promise(resolve => {
+ input.addEventListener("focus", resolve, { once: true });
+ });
+ input.focus();
+ await focusPromise;
+ return input.getBoundingClientRect();
+ });
+
+ // Open the select popup.
+ const selectPopup = await openSelectPopup(
+ iframeX + selectRect.x + selectRect.width / 2,
+ iframeY + selectRect.y + selectRect.height / 2,
+ newWin
+ );
+
+ // Check the coordinates of 'selectPopup'.
+ let popupRect = selectPopup.getBoundingClientRect();
+ is(
+ popupRect.x,
+ iframeX +
+ iframeBorderLeft +
+ selectRect.x -
+ newWin.mozInnerScreenX +
+ parseFloat(getComputedStyle(selectPopup).marginLeft),
+ "x position of the popup"
+ );
+
+ let expectedYPosition =
+ iframeY +
+ selectRect.y +
+ iframeBorderTop -
+ newWin.mozInnerScreenY +
+ parseFloat(getComputedStyle(selectPopup).marginTop);
+
+ // On platforms other than macOS the popup menu is positioned below the
+ // option element. On macOS the top is aligned to the selected item (so the
+ // first label).
+ if (navigator.platform.includes("Mac")) {
+ const offsetToSelectedItem =
+ selectPopup.querySelector("menuitem[selected]").getBoundingClientRect()
+ .top - popupRect.top;
+ expectedYPosition -= offsetToSelectedItem;
+ } else {
+ expectedYPosition += selectRect.height;
+ }
+
+ isfuzzy(
+ popupRect.y,
+ expectedYPosition,
+ window.devicePixelRatio,
+ "y position of the popup"
+ );
+
+ await hideSelectPopup("enter", newWin);
+
+ await BrowserTestUtils.closeWindow(newWin);
+});
diff --git a/layout/base/tests/browser_stylesheet_change_events.js b/layout/base/tests/browser_stylesheet_change_events.js
new file mode 100644
index 0000000000..c867197050
--- /dev/null
+++ b/layout/base/tests/browser_stylesheet_change_events.js
@@ -0,0 +1,227 @@
+const gTestRoot = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ "http://127.0.0.1:8888/"
+);
+
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser, url: gTestRoot + "file_stylesheet_change_events.html" },
+ async function (browser) {
+ await SpecialPowers.spawn(
+ browser,
+ [gTestRoot],
+ testApplicableStateChangeEvent
+ );
+ }
+ );
+});
+
+// This function runs entirely in the content process. It doesn't have access
+// any free variables in this file.
+async function testApplicableStateChangeEvent(testRoot) {
+ // We've seen the original stylesheet in the document.
+ // Now add a stylesheet on the fly and make sure we see it.
+ let doc = content.document;
+ doc.styleSheetChangeEventsEnabled = true;
+
+ const unexpectedContentEvent = event =>
+ ok(false, "Received a " + event.type + " event on content");
+ doc.addEventListener(
+ "StyleSheetApplicableStateChanged",
+ unexpectedContentEvent
+ );
+ doc.defaultView.addEventListener(
+ "StyleSheetApplicableStateChanged",
+ unexpectedContentEvent
+ );
+ doc.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+ doc.defaultView.addEventListener("StyleSheetRemoved", unexpectedContentEvent);
+
+ function shouldIgnoreEvent(e) {
+ // accessiblecaret.css might be reported, interfering with the test
+ // assertions, so let's ignore it
+ return (
+ e.stylesheet?.href === "resource://content-accessible/accessiblecaret.css"
+ );
+ }
+
+ function waitForStyleApplicableStateChanged() {
+ return ContentTaskUtils.waitForEvent(
+ docShell.chromeEventHandler,
+ "StyleSheetApplicableStateChanged",
+ true,
+ e => !shouldIgnoreEvent(e)
+ );
+ }
+
+ function waitForStyleSheetRemovedEvent() {
+ return ContentTaskUtils.waitForEvent(
+ docShell.chromeEventHandler,
+ "StyleSheetRemoved",
+ true,
+ e => !shouldIgnoreEvent(e)
+ );
+ }
+
+ function checkApplicableStateChangeEvent(event, { applicable, stylesheet }) {
+ is(
+ event.type,
+ "StyleSheetApplicableStateChanged",
+ "event.type has expected value"
+ );
+ is(event.target, doc, "event targets correct document");
+ is(event.stylesheet, stylesheet, "event.stylesheet has the expected value");
+ is(event.applicable, applicable, "event.applicable has the expected value");
+ }
+
+ function checkStyleSheetRemovedEvent(event, { stylesheet }) {
+ is(event.type, "StyleSheetRemoved", "event.type has expected value");
+ is(event.target, doc, "event targets correct document");
+ is(event.stylesheet, stylesheet, "event.stylesheet has the expected value");
+ }
+
+ // Updating the text content will actually create a new StyleSheet instance,
+ // and so we should get one event for the new instance, and another one for
+ // the removal of the "previous"one.
+ function waitForTextContentChange() {
+ return Promise.all([
+ waitForStyleSheetRemovedEvent(),
+ waitForStyleApplicableStateChanged(),
+ ]);
+ }
+
+ let stateChanged, evt;
+
+ {
+ const gStyleSheet = "stylesheet_change_events.css";
+
+ info("Add <link> and wait for applicable state change event");
+ let linkEl = doc.createElement("link");
+ linkEl.setAttribute("rel", "stylesheet");
+ linkEl.setAttribute("type", "text/css");
+ linkEl.setAttribute("href", testRoot + gStyleSheet);
+
+ stateChanged = waitForStyleApplicableStateChanged();
+ doc.body.appendChild(linkEl);
+ evt = await stateChanged;
+
+ ok(true, "received dynamic style sheet applicable state change event");
+ checkApplicableStateChangeEvent(evt, {
+ stylesheet: linkEl.sheet,
+ applicable: true,
+ });
+
+ stateChanged = waitForStyleApplicableStateChanged();
+ linkEl.sheet.disabled = true;
+ evt = await stateChanged;
+
+ ok(true, "received dynamic style sheet applicable state change event");
+ checkApplicableStateChangeEvent(evt, {
+ stylesheet: linkEl.sheet,
+ applicable: false,
+ });
+
+ info("Remove stylesheet and wait for removed event");
+ const removedStylesheet = linkEl.sheet;
+ const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
+ doc.body.removeChild(linkEl);
+ const removedStyleSheetEvt = await onStyleSheetRemoved;
+
+ ok(true, "received removed sheet event");
+ checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
+ stylesheet: removedStylesheet,
+ });
+ }
+
+ {
+ info("Add <style> node and wait for applicable state changed event");
+ let styleEl = doc.createElement("style");
+ styleEl.textContent = `body { background: tomato; }`;
+
+ stateChanged = waitForStyleApplicableStateChanged();
+ doc.head.appendChild(styleEl);
+ evt = await stateChanged;
+
+ ok(true, "received dynamic style sheet applicable state change event");
+ checkApplicableStateChangeEvent(evt, {
+ stylesheet: styleEl.sheet,
+ applicable: true,
+ });
+
+ info("Updating <style> text content");
+ stateChanged = waitForTextContentChange();
+ const inlineStyleSheetBeforeChange = styleEl.sheet;
+
+ styleEl.textContent = `body { background: gold; }`;
+ const [inlineRemovedEvt, inlineAddedEvt] = await stateChanged;
+
+ ok(true, "received expected style sheet events");
+ checkStyleSheetRemovedEvent(inlineRemovedEvt, {
+ stylesheet: inlineStyleSheetBeforeChange,
+ });
+ checkApplicableStateChangeEvent(inlineAddedEvt, {
+ stylesheet: styleEl.sheet,
+ applicable: true,
+ });
+
+ info("Remove stylesheet and wait for removed event");
+ const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
+
+ const removedInlineStylesheet = styleEl.sheet;
+ styleEl.remove();
+ const removedStyleSheetEvt = await onStyleSheetRemoved;
+
+ ok(true, "received removed style sheet event");
+ checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
+ stylesheet: removedInlineStylesheet,
+ });
+ }
+
+ {
+ info(
+ "Create a custom element and check we get an event for its stylesheet"
+ );
+ stateChanged = waitForStyleApplicableStateChanged();
+ const el = doc.createElement("div");
+ const shadowRoot = el.attachShadow({ mode: "open" });
+ doc.body.appendChild(el);
+ shadowRoot.innerHTML = `
+ <span>custom</span>
+ <style>
+ span { color: salmon; }
+ </style>`;
+ evt = await stateChanged;
+
+ ok(true, "received dynamic style sheet applicable state change event");
+ const shadowStyleEl = shadowRoot.querySelector("style");
+ checkApplicableStateChangeEvent(evt, {
+ stylesheet: shadowStyleEl.sheet,
+ applicable: true,
+ });
+
+ info("Updating <style> text content");
+ stateChanged = waitForTextContentChange();
+ const styleSheetBeforeChange = shadowStyleEl.sheet;
+ shadowStyleEl.textContent = `span { color: cyan; }`;
+ const [removedEvt, addedEvt] = await stateChanged;
+
+ ok(true, "received expected style sheet events");
+ checkStyleSheetRemovedEvent(removedEvt, {
+ stylesheet: styleSheetBeforeChange,
+ });
+ checkApplicableStateChangeEvent(addedEvt, {
+ stylesheet: shadowStyleEl.sheet,
+ applicable: true,
+ });
+
+ info("Remove stylesheet and wait for removed event");
+ const onStyleSheetRemoved = waitForStyleSheetRemovedEvent();
+ const removedShadowStylesheet = shadowStyleEl.sheet;
+ shadowStyleEl.remove();
+ const removedStyleSheetEvt = await onStyleSheetRemoved;
+ ok(true, "received removed style sheet event");
+ checkStyleSheetRemovedEvent(removedStyleSheetEvt, {
+ stylesheet: removedShadowStylesheet,
+ });
+ }
+}
diff --git a/layout/base/tests/browser_visual_viewport_iframe.js b/layout/base/tests/browser_visual_viewport_iframe.js
new file mode 100644
index 0000000000..849ab53caa
--- /dev/null
+++ b/layout/base/tests/browser_visual_viewport_iframe.js
@@ -0,0 +1,59 @@
+"use strict";
+
+add_task(async () => {
+ function httpURL(filename, host = "https://example.com/") {
+ let root = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content/",
+ host
+ );
+ return root + filename;
+ }
+
+ await SpecialPowers.pushPrefEnv({
+ set: [["dom.meta-viewport.enabled", true]],
+ });
+
+ const fissionWindow = await BrowserTestUtils.openNewBrowserWindow({
+ fission: true,
+ });
+ const url = httpURL(
+ "test_visual_viewport_in_oopif.html",
+ "http://mochi.test:8888/"
+ );
+ const crossOriginIframeUrl = httpURL("visual_viewport_in_child.html");
+
+ try {
+ await BrowserTestUtils.withNewTab(
+ { gBrowser: fissionWindow.gBrowser, url },
+ async browser => {
+ await SpecialPowers.spawn(
+ browser,
+ [crossOriginIframeUrl],
+ async iframeUrl => {
+ const iframe = content.document.getElementById("iframe");
+ iframe.setAttribute("src", iframeUrl);
+
+ let { width, height } = await new Promise(resolve => {
+ content.window.addEventListener("message", msg => {
+ resolve(msg.data);
+ });
+ });
+
+ is(
+ width,
+ 300,
+ "visualViewport.width shouldn't be affected in out-of-process iframes"
+ );
+ is(
+ height,
+ 300,
+ "visualViewport.height shouldn't be affected in out-of-process iframes"
+ );
+ }
+ );
+ }
+ );
+ } finally {
+ await BrowserTestUtils.closeWindow(fissionWindow);
+ }
+});
diff --git a/layout/base/tests/bug1007065-1-ref.html b/layout/base/tests/bug1007065-1-ref.html
new file mode 100644
index 0000000000..fe867dfa0b
--- /dev/null
+++ b/layout/base/tests/bug1007065-1-ref.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <input type="text" style="text-align:right; border-width:0;">
+ <script>
+ function start() {
+ var input = document.querySelector("input");
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1007065-1.html b/layout/base/tests/bug1007065-1.html
new file mode 100644
index 0000000000..174ffb888b
--- /dev/null
+++ b/layout/base/tests/bug1007065-1.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <input type="text" style="text-align:right; overflow:hidden; border-width:0;">
+ <script>
+ function start() {
+ var input = document.querySelector("input");
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1007067-1-ref.html b/layout/base/tests/bug1007067-1-ref.html
new file mode 100644
index 0000000000..b303167dd0
--- /dev/null
+++ b/layout/base/tests/bug1007067-1-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+</head>
+<body onload="start()">
+<textarea style="width:500px; height: 200px; border-width:0">-----------------------------------------
+{insert newline before opening bracket}-----------------------------------b</textarea>
+<select><option value="AED">AED - United Arab Emirates Dirham - د.إ</option></select>
+ <script>
+ function start() {
+ var input = document.querySelector("textarea");
+ input.selectionStart = 42
+ input.selectionEnd = 42
+ input.focus();
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1007067-1.html b/layout/base/tests/bug1007067-1.html
new file mode 100644
index 0000000000..6284d13ef2
--- /dev/null
+++ b/layout/base/tests/bug1007067-1.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+<meta charset="utf-8">
+</head>
+<body onload="start()">
+<textarea style="width:500px; height: 200px; border-width:0">-----------------------------------------{insert newline before opening bracket}-----------------------------------b</textarea>
+<select><option value="AED">AED - United Arab Emirates Dirham - د.إ</option></select>
+ <script>
+ function start() {
+ var input = document.querySelector("textarea");
+ input.selectionStart = 41
+ input.selectionEnd = 41
+ input.focus();
+ window.parent.synthesizeKey("VK_RETURN", { }, window)
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1061468-ref.html b/layout/base/tests/bug1061468-ref.html
new file mode 100644
index 0000000000..9a3330c9fe
--- /dev/null
+++ b/layout/base/tests/bug1061468-ref.html
@@ -0,0 +1,13 @@
+<html>
+<head></head>
+<body>
+
+<div id="firstDiv">
+Parent1
+</div>
+
+<div id="secondDiv">
+Parent2<div contenteditable id="editable">Testing 1</div></div>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug1061468.html b/layout/base/tests/bug1061468.html
new file mode 100644
index 0000000000..be538fe0ae
--- /dev/null
+++ b/layout/base/tests/bug1061468.html
@@ -0,0 +1,40 @@
+<html>
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script>
+function runTest() {
+ var sel = window.getSelection();
+ var r = new Range()
+ r.setStart(document.querySelector("#firstDiv"),0);
+ r.setEnd(document.querySelector("#firstDiv"),1);
+ sel.addRange(r)
+
+ document.querySelector("#editable").focus();
+ document.querySelector("#secondDiv").appendChild(document.querySelector("#editable"));
+
+ is(sel.rangeCount, 1, "still have a range in Selection")
+ var s=""
+ try {
+ var r2 = sel.getRangeAt(0)
+ s+=r2.startContainer.tagName
+ s+=r2.startOffset
+ s+=r2.endContainer.tagName
+ s+=r2.endOffset
+ } catch(e) {}
+
+ is(s, "DIV1DIV1", "the range gravitated correctly")
+}
+</script>
+</head>
+<body onload="runTest()">
+
+<div id="firstDiv">
+Parent1
+<div contenteditable id="editable">Testing 1</div>
+</div>
+
+<div id="secondDiv">
+Parent2</div>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-1-ref.html b/layout/base/tests/bug106855-1-ref.html
new file mode 100644
index 0000000000..9ed8da4fc2
--- /dev/null
+++ b/layout/base/tests/bug106855-1-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RIGHT'); // now after "A"
+ sendKey('RIGHT'); //
+ sendKey('RIGHT'); //
+ sendKey('RIGHT'); // now at the last line
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-1.html b/layout/base/tests/bug106855-1.html
new file mode 100644
index 0000000000..9bba203c8a
--- /dev/null
+++ b/layout/base/tests/bug106855-1.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('DOWN'); // now after "A"
+ sendKey('DOWN'); //
+ sendKey('DOWN'); // now at the last line
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug106855-2.html b/layout/base/tests/bug106855-2.html
new file mode 100644
index 0000000000..040d033632
--- /dev/null
+++ b/layout/base/tests/bug106855-2.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x<br>
+<textarea id="t" rows="4" spellcheck="false" style="-moz-appearance: none">
+A
+
+
+</textarea><br>
+y
+<script>
+ // Position the caret at the last line
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('DOWN'); // now after "A"
+ sendKey('DOWN'); //
+ sendKey('DOWN'); //
+ sendKey('DOWN'); // now at the last line
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1078327_inner.html b/layout/base/tests/bug1078327_inner.html
new file mode 100644
index 0000000000..41fe05a099
--- /dev/null
+++ b/layout/base/tests/bug1078327_inner.html
@@ -0,0 +1,107 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1078327
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1078327</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #listener { background: yellow; padding: 10px; }
+ #mediator { background: red; padding: 20px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_capture = false;
+ var test_mediator_over = false;
+ var test_mediator_move = false;
+ var test_mediator_out = false;
+ var test_listener = false;
+ var test_lost_capture = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type + ". Mediator.setPointerCapture()");
+ mediator.setPointerCapture(event.pointerId);
+ test_target = true;
+ test_capture = true;
+ }
+ function MediatorHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ if(!test_capture)
+ return;
+ if(event.type == "pointermove")
+ test_mediator_move++;
+ if(event.type == "pointerover")
+ test_mediator_over++;
+ if(event.type == "pointerout")
+ test_mediator_out++;
+ if(event.type == "lostpointercapture")
+ test_lost_capture = true;
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetHandler);
+ mediator.addEventListener("gotpointercapture", MediatorHandler);
+ mediator.addEventListener("pointerover", MediatorHandler);
+ mediator.addEventListener("pointermove", MediatorHandler);
+ mediator.addEventListener("pointerout", MediatorHandler);
+ mediator.addEventListener("lostpointercapture", MediatorHandler);
+ listener.addEventListener("pointermove", ListenerHandler);
+ var rect_t = target.getBoundingClientRect();
+ var rect_m = mediator.getBoundingClientRect();
+ var rect_l = listener.getBoundingClientRect();
+ synthesizeMouse(target, rect_t.width/2, rect_t.height/20, {type: "mousedown"});
+ synthesizeMouse(target, rect_t.width/2, rect_t.height/20, {type: "mousemove"});
+ synthesizeMouse(mediator, rect_m.width/2, rect_m.height/20, {type: "mousemove"});
+ synthesizeMouse(listener, rect_l.width/2, rect_l.height/20, {type: "mousemove"});
+ synthesizeMouse(mediator, rect_m.width/2, rect_m.height/20, {type: "mousemove"});
+ synthesizeMouse(target, rect_t.width/2, rect_t.height/20, {type: "mousemove"});
+ synthesizeMouse(target, rect_t.width/2, rect_t.height/20, {type: "mouseup"});
+ synthesizeMouse(target, rect_t.width/2, rect_t.height/20, {type: "mousemove"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "pointerdown event should be received by target");
+ parent.is(test_lost_capture, true, "mediator should receive lostpointercapture");
+ parent.is(test_mediator_over, 1, "mediator should receive pointerover event only once");
+ parent.is(test_mediator_move, 5, "mediator should receive pointermove event five times");
+ parent.is(test_mediator_out, 1, "mediator should receive pointerout event only once");
+ parent.is(test_listener, false, "listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1078327">Mozilla Bug 1078327</a>
+ <div id="mediator">
+ <div id="listener">div id=listener</div>
+ </div>
+ <div id="target">div id=target</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1080360_inner.html b/layout/base/tests/bug1080360_inner.html
new file mode 100644
index 0000000000..0d264e9dc0
--- /dev/null
+++ b/layout/base/tests/bug1080360_inner.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080360
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080360</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #listener { background: yellow; padding: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_listener_got = false;
+ var test_listener_lost = false;
+ var test_document = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type);
+ listener.setPointerCapture(event.pointerId);
+ test_target = true;
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ if(event.type == "gotpointercapture") {
+ test_listener_got = true;
+ listener.remove();
+ }
+ if(event.type == "lostpointercapture")
+ test_listener_lost = true;
+ }
+ function DocumentHandler(event) {
+ logger("Document receive event: " + event.type);
+ if(event.type == "lostpointercapture")
+ test_document = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetHandler);
+ listener.addEventListener("gotpointercapture", ListenerHandler);
+ listener.addEventListener("lostpointercapture", ListenerHandler);
+ document.addEventListener("lostpointercapture", DocumentHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "pointerdown event should be received by target");
+ parent.is(test_listener_got, true, "gotpointercapture event should be received by listener");
+ parent.is(test_listener_lost, false, "listener should not receive lostpointercapture event");
+ parent.is(test_document, true, "document should receive lostpointercapture event");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080360">Mozilla Bug 1080360</a>
+ <div id="target">div id=target</div>
+ <div id="listener">div id=listener</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1080361_inner.html b/layout/base/tests/bug1080361_inner.html
new file mode 100644
index 0000000000..ba3edf9e2c
--- /dev/null
+++ b/layout/base/tests/bug1080361_inner.html
@@ -0,0 +1,114 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080361
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080361</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #mediator, #listener { background: yellow; margin: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_target_down = false;
+ var test_target_up = false;
+ var test_first_exc = false;
+ var test_second_exc = false;
+ var test_third_exc = false;
+ var test_fourth_exc = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ test_target_down = true;
+ try {
+ logger("target.setPointerCapture()");
+ target.setPointerCapture(31415);
+ } catch(exc) {
+ test_first_exc = true;
+ parent.is(exc.name, "NotFoundError", "Exception NotFoundError should be fired");
+ }
+ try {
+ logger("mediator.setPointerCapture()");
+ mediator.remove();
+ mediator.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_second_exc = true;
+ parent.is(exc.name, "InvalidStateError", "Exception InvalidStateError should be fired");
+ }
+ try {
+ logger("listener.setPointerCapture()");
+ listener.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_third_exc = true;
+ }
+ }
+ function TargetUpHandler(event) {
+ logger("Target receive event: " + event.type);
+ test_target_up = true;
+ try {
+ logger("target.setPointerCapture()");
+ target.setPointerCapture(event.pointerId);
+ } catch(exc) {
+ test_fourth_exc = true;
+ }
+ }
+ function ListenerHandler(event) {
+ logger("Listener receive event: " + event.type);
+ test_listener = true;
+ listener.releasePointerCapture(event.pointerId);
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ target.addEventListener("pointerup", TargetUpHandler);
+ listener.addEventListener("gotpointercapture", ListenerHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ SimpleTest.executeSoon(function() {
+ parent.is(test_target_down, true, "pointerdown event should be received by target");
+ parent.is(test_target_up, true, "pointerup event should be received by target");
+ parent.is(test_first_exc, true, "first exception should be thrown");
+ parent.is(test_second_exc, true, "second exception should be thrown");
+ parent.is(test_third_exc, false, "third exception should not be thrown");
+ parent.is(test_fourth_exc, false, "fourth exception should not be thrown");
+ parent.is(test_listener, true, "listener should receive gotpointercapture event");
+ logger("finishTest");
+ parent.finishTest();
+ });
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1080361">Mozilla Bug 1080361</a>
+ <div id="target">div id=target</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="listener">div id=listener</div>
+ <pre id="log"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1082486-1-ref.html b/layout/base/tests/bug1082486-1-ref.html
new file mode 100644
index 0000000000..043779cb90
--- /dev/null
+++ b/layout/base/tests/bug1082486-1-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ /* Eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+ <input id='i' value="abcdefghd" style="text-indent: -10px">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-1.html b/layout/base/tests/bug1082486-1.html
new file mode 100644
index 0000000000..767a5fd1c3
--- /dev/null
+++ b/layout/base/tests/bug1082486-1.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <style>
+ /* Eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ caret-color: transparent; /* This tests AccessibleCaret, hide the regular caret */
+ }
+ </style>
+ </head>
+ <body onload="focusInput();">
+ <script>
+ function focusInput() {
+ var inp = document.getElementById('i');
+ inp.focus();
+ }
+ </script>
+
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The caret will not be seen when the input is focused. -->
+ <input id='i' value="abcdefghd" style="text-indent: -10px">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-2-ref.html b/layout/base/tests/bug1082486-2-ref.html
new file mode 100644
index 0000000000..5b61a54b6a
--- /dev/null
+++ b/layout/base/tests/bug1082486-2-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body>
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The whole input will not be seen. -->
+ <input id='i' value="abcdefghd" style="position: absolute; top: -100px; left: 0px;">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1082486-2.html b/layout/base/tests/bug1082486-2.html
new file mode 100644
index 0000000000..d367883eb7
--- /dev/null
+++ b/layout/base/tests/bug1082486-2.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ </head>
+ <body onload="document.getElementById('i').focus();">
+ <a target="_blank" href="https://bugzil.la/1082486">Mozilla Bug 1082486</a>
+
+ <!-- The whole input will not be seen. -->
+ <input id='i' value="abcdefghd" style="position: absolute; top: -100px; left: 0px;">
+ </body>
+</html>
diff --git a/layout/base/tests/bug1093686_inner.html b/layout/base/tests/bug1093686_inner.html
new file mode 100644
index 0000000000..8d2b696d96
--- /dev/null
+++ b/layout/base/tests/bug1093686_inner.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<head>
+ <title>Testing effect of listener on body</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>
+ .target { position:absolute; left:200px; top:200px; width:200px; height:200px; background:blue; }
+ </style>
+</head>
+<body id="body" onload="setTimeout(runTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
+<div id="content">
+ <div class="target" id="t"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+var eventTarget;
+window.onmousedown = function(event) { eventTarget = event.target; };
+
+// Make sure the target div is "clickable" by adding a click listener on it.
+document.getElementById('t').addEventListener('click', function(e) {
+ parent.ok(true, "target was clicked on");
+});
+
+// Helper functions
+
+function testMouseClick(aX, aY, aExpectedId, aMsg) {
+ eventTarget = null;
+ synthesizeMouseAtPoint(aX, aY, {});
+ try {
+ parent.is(eventTarget.id, aExpectedId,
+ "checking offset " + aX + "," + aY + " hit " + aExpectedId + " [" + aMsg + "]");
+ } catch (ex) {
+ parent.ok(false, "checking offset " + aX + "," + aY + " hit " + aExpectedId + " [" + aMsg + "]; got " + eventTarget);
+ }
+}
+
+function testWithAndWithoutBodyListener(aX, aY, aExpectedId, aMsg) {
+ var func = function(e) {
+ // no-op function
+ parent.ok(true, "body was clicked on");
+ };
+ testMouseClick(aX, aY, aExpectedId, aMsg + " without listener on body");
+ document.body.addEventListener("click", func);
+ testMouseClick(aX, aY, aExpectedId, aMsg + " with listener on body");
+ document.body.removeEventListener("click", func);
+}
+
+// Main tests
+
+var mm;
+function runTest() {
+ mm = SpecialPowers.getDOMWindowUtils(parent).physicalMillimeterInCSSPixels;
+ parent.ok(4*mm >= 10, "WARNING: mm " + mm + " too small in this configuration. Test results will be bogus");
+
+ // Test near the target, check it hits the target
+ testWithAndWithoutBodyListener(200 - 2*mm, 200 - 2*mm, "t", "basic click retargeting");
+ // Test on the target, check it hits the target
+ testWithAndWithoutBodyListener(200 + 2*mm, 200 + 2*mm, "t", "direct click");
+ // Test outside the target, check it hits the root
+ testWithAndWithoutBodyListener(40, 40, "body", "click way outside target");
+
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.enabled", false]]}, runTest2);
+}
+
+function runTest2() {
+ // In this test, mouse event retargeting is disabled.
+
+ // Test near the target, check it hits the body
+ testWithAndWithoutBodyListener(200 - 2*mm, 200 - 2*mm, "body", "basic click retargeting");
+ // Test on the target, check it hits the target
+ testWithAndWithoutBodyListener(200 + 2*mm, 200 + 2*mm, "t", "direct click");
+ // Test outside the target, check it hits the root
+ testWithAndWithoutBodyListener(40, 40, "body", "click way outside target");
+
+ parent.finishTest();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1097242-1-ref.html b/layout/base/tests/bug1097242-1-ref.html
new file mode 100644
index 0000000000..dd62f18eec
--- /dev/null
+++ b/layout/base/tests/bug1097242-1-ref.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function test() {
+ focus();
+ var div = document.querySelector("div");
+ div.focus();
+ getSelection().collapse(div.firstChild, 0);
+ }
+ </script>
+ <body onload="test()">
+ <div contenteditable spellcheck="false" style="outline: none">foo<span>bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1097242-1.html b/layout/base/tests/bug1097242-1.html
new file mode 100644
index 0000000000..e94a8c9917
--- /dev/null
+++ b/layout/base/tests/bug1097242-1.html
@@ -0,0 +1,22 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function focused() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body>
+ <div contenteditable spellcheck="false" onfocus="focused()"
+ style="outline: none">foo<span contenteditable=false
+ style="-moz-user-select: none">bar</span>baz</div>
+ <script>
+ SimpleTest.waitForFocus(() => {
+ SimpleTest.executeSoon(() => {
+ synthesizeMouseAtCenter(document.querySelector("span"), {});
+ });
+ });
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-1-ref.html b/layout/base/tests/bug1109968-1-ref.html
new file mode 100644
index 0000000000..28bcf608a7
--- /dev/null
+++ b/layout/base/tests/bug1109968-1-ref.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><a href="#">bar</a></div>baz</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "baz"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-1.html b/layout/base/tests/bug1109968-1.html
new file mode 100644
index 0000000000..f5b2871168
--- /dev/null
+++ b/layout/base/tests/bug1109968-1.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="setTimeout(done, 0)" contenteditable>foo<div contenteditable="false"><a href="#">bar</a></div>baz</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "baz"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-2-ref.html b/layout/base/tests/bug1109968-2-ref.html
new file mode 100644
index 0000000000..9638c35b33
--- /dev/null
+++ b/layout/base/tests/bug1109968-2-ref.html
@@ -0,0 +1,17 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<div contenteditable="false"><img src="image_rgrg-256x256.png" width="10" height="10"></div>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1109968-2.html b/layout/base/tests/bug1109968-2.html
new file mode 100644
index 0000000000..22416b30f0
--- /dev/null
+++ b/layout/base/tests/bug1109968-2.html
@@ -0,0 +1,23 @@
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="setTimeout(done, 0)" contenteditable>foo<div contenteditable="false"><img src="image_rgrg-256x256.png" width="10" height="10"></div>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1123067-1.html b/layout/base/tests/bug1123067-1.html
new file mode 100644
index 0000000000..86f30fa39c
--- /dev/null
+++ b/layout/base/tests/bug1123067-1.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1123067</title>
+ <script>
+ function click(id) {
+ var e = document.querySelector(id);
+ synthesizeMouse(e, 1, 1, {type: "mousedown"}, window);
+ synthesizeMouse(e, 1, 1, {type: "mouseup"}, window);
+ }
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ click('#test1')
+ }
+ </script>
+ <style>
+ div { -moz-user-select:none; }
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="setTimeout(test, 0)">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-2.html b/layout/base/tests/bug1123067-2.html
new file mode 100644
index 0000000000..2d3d593ee2
--- /dev/null
+++ b/layout/base/tests/bug1123067-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var e = document.querySelector('#test1');
+ e.focus();
+ }
+ </script>
+ <style>
+ div { -moz-user-select:none; }
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="setTimeout(test,0)">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-3.html b/layout/base/tests/bug1123067-3.html
new file mode 100644
index 0000000000..54468d2804
--- /dev/null
+++ b/layout/base/tests/bug1123067-3.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait" style="-moz-user-select:none" spellcheck="false">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.designMode='on';
+ document.querySelector('div').focus();
+ document.body.offsetHeight;
+ setTimeout(test,0)
+ }
+ </script>
+ <style>
+ div { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div>This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1123067-ref.html b/layout/base/tests/bug1123067-ref.html
new file mode 100644
index 0000000000..3fd5c6065f
--- /dev/null
+++ b/layout/base/tests/bug1123067-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase for bug 1123067</title>
+ <script>
+ function test() {
+ for (var i = 0; i < 5; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var e = document.querySelector('#test1');
+ e.focus();
+ }
+ </script>
+ <style>
+ div:focus { outline:1px solid black; }
+ </style>
+</head>
+<body>
+
+<div id="test1" contenteditable="true" spellcheck="false" onfocus="setTimeout(test, 0)">This text is NOT selectable.</div>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1132768-1-ref.html b/layout/base/tests/bug1132768-1-ref.html
new file mode 100644
index 0000000000..0fea14cf0a
--- /dev/null
+++ b/layout/base/tests/bug1132768-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <script>
+ function test() {
+ focus();
+ getSelection().selectAllChildren(document.querySelector("span"));
+ }
+ </script>
+ <body onload="test()">
+ <div>foo<span>bar</span>baz</div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1132768-1.html b/layout/base/tests/bug1132768-1.html
new file mode 100644
index 0000000000..9b062b7d85
--- /dev/null
+++ b/layout/base/tests/bug1132768-1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <title>Can drag-select non-editable content inside editable content</title>
+ <script src="selection-utils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <div contenteditable spellcheck="false"
+ style="outline: none">foo<span contenteditable=false>bar</span>baz</div>
+ <script>
+ SimpleTest.waitForFocus(function() {
+ const span = document.querySelector("span");
+ const rect = span.getBoundingClientRect();
+ dragSelectPoints(span, 0, rect.height / 2, rect.width, rect.height / 2);
+ setTimeout(() => document.documentElement.removeAttribute("class"));
+ });
+ </script>
+</html>
diff --git a/layout/base/tests/bug1153130_inner.html b/layout/base/tests/bug1153130_inner.html
new file mode 100644
index 0000000000..aee8b6792a
--- /dev/null
+++ b/layout/base/tests/bug1153130_inner.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1153130</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target { background: yellow; padding: 10px; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var test_down = false;
+ var test_capture = false;
+ var test_move = false;
+ var test_success = false;
+
+ function TargetHandler(event) {
+ logger("Target receive event: " + event.type);
+ if(event.type == "pointerdown") {
+ test_down = true;
+ target.setPointerCapture(event.pointerId);
+ } else if(event.type == "gotpointercapture") {
+ test_capture = true;
+ } else if(event.type == "pointermove" && test_capture) {
+ test_move = true;
+ }
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('target');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest() {
+ logger("executeTest");
+ target = document.getElementById("target");
+ target.addEventListener("pointerdown", TargetHandler);
+ target.addEventListener("gotpointercapture", TargetHandler);
+ target.addEventListener("pointermove", TargetHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/5, rect.height/5, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/5, rect.height/5, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/4, rect.height/4, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/3, rect.height/3, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/3, rect.height/3, {type: "mouseup"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousemove"});
+ test_success = true;
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down, true, "pointerdown event should be received by target");
+ parent.is(test_capture, true, "gotpointercapture event should be received by target");
+ parent.is(test_move, true, "pointermove event should be received by target while pointer capture is active");
+ parent.is(test_success, true, "Firefox should be live!");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1153130">Mozilla Bug 1153130</a>
+ <div id="target">div id=target</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug1162990_inner_1.html b/layout/base/tests/bug1162990_inner_1.html
new file mode 100644
index 0000000000..fc6a855c61
--- /dev/null
+++ b/layout/base/tests/bug1162990_inner_1.html
@@ -0,0 +1,146 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ div#basket {
+ background: red;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#target {
+ background: lightgreen;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#child {
+ background: lightblue;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#listener {
+ background: yellow;
+ padding: 10px;
+ margin: 10px;
+ }
+ </style>
+ <script type="application/javascript">
+ var basket;
+ var target;
+ var child;
+ var listener;
+
+ var test_basketLeave = 0;
+ var test_targetGotCapture = 0;
+ var test_targetLostCapture = 0;
+ var test_targetLeave = 0;
+ var test_childLeave = 0;
+ var test_listenerDown = 0;
+ var test_listenerLeave = 0;
+
+ function basketLeaveHandler(event) {
+ logger("basket: " + event.type);
+ test_basketLeave++;
+ }
+ function targetGotHandler(event) {
+ logger("target: " + event.type);
+ test_targetGotCapture++;
+ }
+ function targetLostHandler(event) {
+ logger("target: " + event.type);
+ test_targetLostCapture++;
+ }
+ function targetLeaveHandler(event) {
+ logger("target: " + event.type);
+ test_targetLeave++;
+ }
+ function childLeaveHandler(event) {
+ logger("child: " + event.type);
+ test_childLeave++;
+ }
+ function listenerDownHandler(event) {
+ logger("listener: " + event.type);
+ target.setPointerCapture(event.pointerId);
+ test_listenerDown++;
+ }
+ function listenerLeaveHandler(event) {
+ logger("listener: " + event.type);
+ test_listenerLeave++;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+
+ function setEventHandlers() {
+ basket = document.getElementById("basket");
+ target = document.getElementById("target");
+ child = document.getElementById("child");
+ listener = document.getElementById("listener");
+
+ basket.addEventListener("pointerleave", basketLeaveHandler);
+ target.addEventListener("gotpointercapture", targetGotHandler);
+ target.addEventListener("lostpointercapture", targetLostHandler);
+ target.addEventListener("pointerleave", targetLeaveHandler);
+ child.addEventListener("pointerleave", childLeaveHandler);
+ listener.addEventListener("pointerdown", listenerDownHandler);
+ listener.addEventListener("pointerleave", listenerLeaveHandler);
+ }
+
+ function executeTest()
+ {
+ logger("executeTest");
+ setEventHandlers();
+ var rectCd = child.getBoundingClientRect();
+ var rectLr = listener.getBoundingClientRect();
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/2, {type: "mousedown"});
+ synthesizeMouse(child, rectCd.width/3, rectCd.height/2, {type: "mousemove"});
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/2, {type: "mousemove"});
+ synthesizeMouse(child, rectCd.width/3, rectCd.height/2, {type: "mousemove"});
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/2, {type: "mousemove"});
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/2, {type: "mouseup"});
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/3, {type: "mousemove"});
+ finishTest();
+ }
+
+ function finishTest() {
+ parent.is(test_basketLeave, 1, "Part1: basket should receive pointerleave event after pointer capturing");
+ parent.is(test_targetGotCapture, 1, "Part1: target should receive gotpointercapture event");
+ parent.is(test_targetLostCapture, 1, "Part1: target should receive lostpointercapture event");
+ parent.is(test_targetLeave, 1, "Part1: target should receive pointerleave event only one time");
+ parent.is(test_childLeave, 0, "Part1: child should not receive pointerleave event after pointer capturing");
+ parent.is(test_listenerDown, 1, "Part1: listener should receive pointerdown event");
+ parent.is(test_listenerLeave, 1, "Part1: listener should receive pointerleave event only one time");
+ logger("finishTest");
+ parent.finishTest();
+ }
+
+ function logger(message) {
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162990">Mozilla Bug 1162990 Test 1</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="basket">div id=basket
+ <div id="target">div id=target
+ <div id="child">div id=child</div>
+ </div>
+ </div>
+ <div id="listener">div id=listener</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1162990_inner_2.html b/layout/base/tests/bug1162990_inner_2.html
new file mode 100644
index 0000000000..2125860f28
--- /dev/null
+++ b/layout/base/tests/bug1162990_inner_2.html
@@ -0,0 +1,147 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ div#basket {
+ background: red;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#target {
+ background: lightgreen;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#child {
+ background: lightblue;
+ padding: 10px;
+ margin: 10px;
+ }
+ div#listener {
+ background: yellow;
+ padding: 10px;
+ margin: 10px;
+ }
+ </style>
+ <script type="application/javascript">
+ var basket;
+ var target;
+ var child;
+ var listener;
+
+ var test_basketLeave = 0;
+ var test_targetDown = 0;
+ var test_targetGotCapture = 0;
+ var test_targetLostCapture = 0;
+ var test_targetLeave = 0;
+ var test_childLeave = 0;
+ var test_listenerLeave = 0;
+
+ function basketLeaveHandler(event) {
+ logger("basket: " + event.type);
+ test_basketLeave++;
+ }
+ function targetDownHandler(event) {
+ logger("target: " + event.type);
+ target.setPointerCapture(event.pointerId);
+ test_targetDown++;
+ }
+ function targetGotHandler(event) {
+ logger("target: " + event.type);
+ test_targetGotCapture++;
+ }
+ function targetLostHandler(event) {
+ logger("target: " + event.type);
+ test_targetLostCapture++;
+ }
+ function targetLeaveHandler(event) {
+ logger("target: " + event.type);
+ test_targetLeave++;
+ }
+ function childLeaveHandler(event) {
+ logger("child: " + event.type);
+ test_childLeave++;
+ }
+ function listenerLeaveHandler(event) {
+ logger("listener: " + event.type);
+ test_listenerLeave++;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+
+ function setEventHandlers() {
+ basket = document.getElementById("basket");
+ target = document.getElementById("target");
+ child = document.getElementById("child");
+ listener = document.getElementById("listener");
+
+ basket.addEventListener("pointerleave", basketLeaveHandler);
+ target.addEventListener("pointerdown", targetDownHandler);
+ target.addEventListener("gotpointercapture", targetGotHandler);
+ target.addEventListener("lostpointercapture", targetLostHandler);
+ target.addEventListener("pointerleave", targetLeaveHandler);
+ child.addEventListener("pointerleave", childLeaveHandler);
+ listener.addEventListener("pointerleave", listenerLeaveHandler);
+ }
+
+ function executeTest()
+ {
+ logger("executeTest");
+ setEventHandlers();
+ var rectTg = target.getBoundingClientRect();
+ var rectCd = child.getBoundingClientRect();
+ var rectLr = listener.getBoundingClientRect();
+ synthesizeMouse(target, rectTg.width/3, rectTg.height/7, {type: "mousedown"});
+ synthesizeMouse(child, rectCd.width/3, rectCd.height/2, {type: "mousemove"});
+ synthesizeMouse(listener, rectLr.width/3, rectLr.height/2, {type: "mousemove"});
+ synthesizeMouse(child, rectCd.width/3, rectCd.height/2, {type: "mousemove"});
+ synthesizeMouse(target, rectTg.width/3, rectTg.height/7, {type: "mousemove"});
+ synthesizeMouse(target, rectTg.width/3, rectTg.height/7, {type: "mouseup"});
+ synthesizeMouse(target, rectTg.width/3, rectTg.height/9, {type: "mousemove"});
+ finishTest();
+ }
+
+ function finishTest() {
+ parent.is(test_basketLeave, 0, "Part2: basket should not receive pointerleave event after pointer capturing");
+ parent.is(test_targetDown, 1, "Part2: target should receive pointerdown event");
+ parent.is(test_targetGotCapture, 1, "Part2: target should receive gotpointercapture event");
+ parent.is(test_targetLostCapture, 1, "Part2: target should receive lostpointercapture event");
+ parent.is(test_targetLeave, 0, "Part2: target should not receive pointerleave event");
+ parent.is(test_childLeave, 0, "Part2: child should not receive pointerleave event after pointer capturing");
+ parent.is(test_listenerLeave, 0, "Part2: listener should not receive pointerleave event after pointer capturing");
+ logger("finishTest");
+ parent.finishTest();
+ }
+
+ function logger(message) {
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1162990">Mozilla Bug 1162990 Test 2</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="basket">div id=basket
+ <div id="target">div id=target
+ <div id="child">div id=child</div>
+ </div>
+ </div>
+ <div id="listener">div id=listener</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug1226904.html b/layout/base/tests/bug1226904.html
new file mode 100644
index 0000000000..646608b1c1
--- /dev/null
+++ b/layout/base/tests/bug1226904.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+</head>
+<style>
+ #container {
+ transform-style: preserve-3d;
+ transform: translate3d(400px, 0px, 10px);
+ background-color: black;
+ }
+ #separator {
+ width: 400px;
+ height: 400px;
+ background-color: green;
+ }
+ #transformed {
+ width: 400px;
+ height: 400px;
+ background-color: blue;
+ transform: translate3d(-400px, 0px, 10px);
+ }
+</style>
+<body onload="run()">
+<div>
+ <div id="container">
+ <div id="separator"></div>
+ <div id="transformed"></div>
+ </div>
+</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-1-ref.html b/layout/base/tests/bug1237236-1-ref.html
new file mode 100644
index 0000000000..eed0fafe8f
--- /dev/null
+++ b/layout/base/tests/bug1237236-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowDown"); // move caret to the second line
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-1.html b/layout/base/tests/bug1237236-1.html
new file mode 100644
index 0000000000..e7ea00a15a
--- /dev/null
+++ b/layout/base/tests/bug1237236-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowDown", {shiftKey: true}); // select first line including the newline
+ synthesizeKey("KEY_ArrowRight"); // collapse to the end of the selection
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abc
+def</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-2-ref.html b/layout/base/tests/bug1237236-2-ref.html
new file mode 100644
index 0000000000..8b4ccece76
--- /dev/null
+++ b/layout/base/tests/bug1237236-2-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1237236</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowRight", {repeat: 4});
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="setTimeout(test1, 0)" spellcheck="false">abc
+def</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1237236-2.html b/layout/base/tests/bug1237236-2.html
new file mode 100644
index 0000000000..dea4f93600
--- /dev/null
+++ b/layout/base/tests/bug1237236-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1237236</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre.firstChild, 4/*after the newline*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-1-ref.html b/layout/base/tests/bug1258308-1-ref.html
new file mode 100644
index 0000000000..a84c7e4d95
--- /dev/null
+++ b/layout/base/tests/bug1258308-1-ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1258308</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ // caret should now be at the start of the third line
+ document.body.offsetHeight;
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ document.documentElement.removeAttribute("class");
+ }));
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none;scrollbar-width:none">abc
+def
+ghi</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-1.html b/layout/base/tests/bug1258308-1.html
new file mode 100644
index 0000000000..e6234ca818
--- /dev/null
+++ b/layout/base/tests/bug1258308-1.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1258308</title>
+ <script>
+ function test1() {
+ const kIsMac = navigator.platform.indexOf("Mac") == 0;
+ synthesizeKey("KEY_ArrowDown"); // go to the second line
+ // go to the end of the second line
+ if (kIsMac) {
+ synthesizeKey("KEY_ArrowRight", {accelKey: true});
+ } else {
+ synthesizeKey("KEY_End");
+ }
+ synthesizeKey("KEY_ArrowRight", {shiftKey: true}); // select the newline
+ synthesizeKey("KEY_ArrowRight"); // collapse to the end of the selection
+ // caret should now be at the start of the third line
+ document.body.offsetHeight;
+ requestAnimationFrame(() => requestAnimationFrame(() => {
+ document.documentElement.removeAttribute("class");
+ }));
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none;scrollbar-width:none">abc
+def
+ghi</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-2-ref.html b/layout/base/tests/bug1258308-2-ref.html
new file mode 100644
index 0000000000..3bd51f0f0e
--- /dev/null
+++ b/layout/base/tests/bug1258308-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1258308</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowRight", {repeat: 8});
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="setTimeout(test1, 0)" spellcheck="false">abc
+def
+ghi</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1258308-2.html b/layout/base/tests/bug1258308-2.html
new file mode 100644
index 0000000000..e88ded5f6c
--- /dev/null
+++ b/layout/base/tests/bug1258308-2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1258308</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre.firstChild, 8/*after the 2nd line newline*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abc
+def
+ghi</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-1-ref.html b/layout/base/tests/bug1259949-1-ref.html
new file mode 100644
index 0000000000..7e46c43c28
--- /dev/null
+++ b/layout/base/tests/bug1259949-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowDown");
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abcdef
+</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-1.html b/layout/base/tests/bug1259949-1.html
new file mode 100644
index 0000000000..0be791a54b
--- /dev/null
+++ b/layout/base/tests/bug1259949-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowRight");
+ synthesizeKey("KEY_ArrowRight");
+ synthesizeKey("KEY_ArrowRight"); // caret is now between "c" and "d"
+ synthesizeKey("KEY_ArrowDown", {shiftKey: true}); // select "def\n"
+ synthesizeKey("KEY_ArrowRight"); // collapse to the end of the selection
+ // caret should now be at the start of the second line
+ document.body.offsetHeight;
+ setTimeout(function(){ document.documentElement.removeAttribute("class"); },0);
+ }
+ function runTests() {
+ document.querySelector('textarea').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<textarea onfocus="test1()" spellcheck="false" style="-moz-appearance:none">abcdef
+</textarea>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-2-ref.html b/layout/base/tests/bug1259949-2-ref.html
new file mode 100644
index 0000000000..2a1f686ef3
--- /dev/null
+++ b/layout/base/tests/bug1259949-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1259949</title>
+ <script>
+ function test1() {
+ synthesizeKey("KEY_ArrowRight", {repeat: 7});
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ document.querySelector('pre').focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body>
+
+<pre contenteditable tabindex=1 onfocus="setTimeout(test1, 0)" spellcheck="false">abcdef
+
+</pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1259949-2.html b/layout/base/tests/bug1259949-2.html
new file mode 100644
index 0000000000..ce4b61fe29
--- /dev/null
+++ b/layout/base/tests/bug1259949-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1259949</title>
+ <script>
+ function test1() {
+ document.body.offsetHeight;
+ document.documentElement.removeAttribute("class");
+ }
+ function runTests() {
+ var pre = document.querySelector('pre');
+ window.getSelection().collapse(pre, 1/*after the text*/)
+ pre.focus();
+ document.body.offsetHeight;
+ }
+ </script>
+</head>
+<body onload="runTests()">
+
+<pre contenteditable tabindex=1 onfocus="test1()" spellcheck="false">abcdef
+<br></pre>
+
+<script>
+SimpleTest.waitForFocus(runTests);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263288-ref.html b/layout/base/tests/bug1263288-ref.html
new file mode 100644
index 0000000000..aef3e45d5d
--- /dev/null
+++ b/layout/base/tests/bug1263288-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263288
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="readonly">xyz</div>
+<div id="editable" spellcheck=false contenteditable='true' style="outline: 1px solid;"><span>xyz</span><br></br></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263288.html b/layout/base/tests/bug1263288.html
new file mode 100644
index 0000000000..e89452c1df
--- /dev/null
+++ b/layout/base/tests/bug1263288.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263288
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263288</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="readonly">xyz</div>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span contentEditable='false'>xyz<!-- comment --></span><br></br></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ synthesizeMouse(theDiv, 100, 2, {});
+ synthesizeKey("KEY_End");
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-1-ref.html b/layout/base/tests/bug1263357-1-ref.html
new file mode 100644
index 0000000000..a846a28983
--- /dev/null
+++ b/layout/base/tests/bug1263357-1-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><tt>xyz<br></tt><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theP = document.getElementById("theP");
+ theDiv.focus();
+ sel.collapse(theP, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-1.html b/layout/base/tests/bug1263357-1.html
new file mode 100644
index 0000000000..bf620a7717
--- /dev/null
+++ b/layout/base/tests/bug1263357-1.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><tt>xyz</tt><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ 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");
+ }
+ synthesizeKey("KEY_Enter", {shiftKey: true});
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-2-ref.html b/layout/base/tests/bug1263357-2-ref.html
new file mode 100644
index 0000000000..9f3844fcde
--- /dev/null
+++ b/layout/base/tests/bug1263357-2-ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p id="theP"><font color=red><span>xyz<br></span></font><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theP = document.getElementById("theP");
+ theDiv.focus();
+ sel.collapse(theP, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-2.html b/layout/base/tests/bug1263357-2.html
new file mode 100644
index 0000000000..cf6277a3cd
--- /dev/null
+++ b/layout/base/tests/bug1263357-2.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #2 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><p><font color=red><span>xyz</span></font><br></p></div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 0);
+ 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");
+ }
+ synthesizeKey("KEY_Enter", {shiftKey: true});
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-3-ref.html b/layout/base/tests/bug1263357-3-ref.html
new file mode 100644
index 0000000000..fe3310b38a
--- /dev/null
+++ b/layout/base/tests/bug1263357-3-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #3 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-3.html b/layout/base/tests/bug1263357-3.html
new file mode 100644
index 0000000000..5b5e405514
--- /dev/null
+++ b/layout/base/tests/bug1263357-3.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #3 for bug1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span><br>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-4-ref.html b/layout/base/tests/bug1263357-4-ref.html
new file mode 100644
index 0000000000..dbd5ac1256
--- /dev/null
+++ b/layout/base/tests/bug1263357-4-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #4 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-4.html b/layout/base/tests/bug1263357-4.html
new file mode 100644
index 0000000000..05ca3de575
--- /dev/null
+++ b/layout/base/tests/bug1263357-4.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #4 for bug1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span>more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-5-ref.html b/layout/base/tests/bug1263357-5-ref.html
new file mode 100644
index 0000000000..e98a620e46
--- /dev/null
+++ b/layout/base/tests/bug1263357-5-ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #5 for bug 1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theDiv.focus();
+ sel.collapse(theDiv, 1);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1263357-5.html b/layout/base/tests/bug1263357-5.html
new file mode 100644
index 0000000000..aa98d17578
--- /dev/null
+++ b/layout/base/tests/bug1263357-5.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML><html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=bug1263357
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Testcase #5 for bug1263357</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div id="editable" contenteditable='true' spellcheck=false style="outline: 1px solid;"><span id="theSpan">text<br></span> more</div>
+<script>
+
+function start() {
+ var sel = window.getSelection();
+ // Focus on editable block.
+ theDiv = document.getElementById("editable");
+ theSpan = document.getElementById("theSpan");
+ theDiv.focus();
+ sel.collapse(theSpan, 2);
+}
+
+SimpleTest.waitForFocus(start);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1354478-1-ref.html b/layout/base/tests/bug1354478-1-ref.html
new file mode 100644
index 0000000000..be2af0e331
--- /dev/null
+++ b/layout/base/tests/bug1354478-1-ref.html
@@ -0,0 +1,35 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-1.html b/layout/base/tests/bug1354478-1.html
new file mode 100644
index 0000000000..bfec0df4e8
--- /dev/null
+++ b/layout/base/tests/bug1354478-1.html
@@ -0,0 +1,35 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-2-ref.html b/layout/base/tests/bug1354478-2-ref.html
new file mode 100644
index 0000000000..ba35b49820
--- /dev/null
+++ b/layout/base/tests/bug1354478-2-ref.html
@@ -0,0 +1,35 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-2.html b/layout/base/tests/bug1354478-2.html
new file mode 100644
index 0000000000..fac2a05097
--- /dev/null
+++ b/layout/base/tests/bug1354478-2.html
@@ -0,0 +1,35 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-3-ref.html b/layout/base/tests/bug1354478-3-ref.html
new file mode 100644
index 0000000000..fc3576a3de
--- /dev/null
+++ b/layout/base/tests/bug1354478-3-ref.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-rl;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-3.html b/layout/base/tests/bug1354478-3.html
new file mode 100644
index 0000000000..df36f23ff0
--- /dev/null
+++ b/layout/base/tests/bug1354478-3.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-rl;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-4-ref.html b/layout/base/tests/bug1354478-4-ref.html
new file mode 100644
index 0000000000..64d4a9722f
--- /dev/null
+++ b/layout/base/tests/bug1354478-4-ref.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-rl;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-4.html b/layout/base/tests/bug1354478-4.html
new file mode 100644
index 0000000000..4bf4e1ee8d
--- /dev/null
+++ b/layout/base/tests/bug1354478-4.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-rl;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-5-ref.html b/layout/base/tests/bug1354478-5-ref.html
new file mode 100644
index 0000000000..1a49897686
--- /dev/null
+++ b/layout/base/tests/bug1354478-5-ref.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-lr;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-5.html b/layout/base/tests/bug1354478-5.html
new file mode 100644
index 0000000000..02bde22e87
--- /dev/null
+++ b/layout/base/tests/bug1354478-5.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-lr;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">aa</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-6-ref.html b/layout/base/tests/bug1354478-6-ref.html
new file mode 100644
index 0000000000..3e0e9b1623
--- /dev/null
+++ b/layout/base/tests/bug1354478-6-ref.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-lr;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1354478-6.html b/layout/base/tests/bug1354478-6.html
new file mode 100644
index 0000000000..badf030d67
--- /dev/null
+++ b/layout/base/tests/bug1354478-6.html
@@ -0,0 +1,38 @@
+<html class="reftest-wait">
+ <head>
+ <meta charset="utf-8">
+ <style>
+ body {
+ writing-mode: sideways-lr;
+ }
+ textarea {
+ inline-size: 200px;
+ -moz-appearance: none;
+ appearance: none;
+ unicode-bidi: plaintext;
+ }
+ div {
+ inline-size: 200px;
+ block-size: 20px;
+ position: relative;
+ inset-block-start: -20px;
+ background: silver;
+ }
+ </style>
+ <script>
+ function start() {
+ var textarea = document.querySelector("textarea");
+ textarea.selectionStart = 1; // place caret between the letters
+ textarea.selectionEnd = 1;
+ textarea.focus();
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()">دد</textarea>
+ <div></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1359411-ref.html b/layout/base/tests/bug1359411-ref.html
new file mode 100644
index 0000000000..939a2f4e63
--- /dev/null
+++ b/layout/base/tests/bug1359411-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html><head><style>
+ i:before{content:"X"}
+</style></head><body onload="runTest()"><div><button type="button"><i></i></button><p></p></div><editor contenteditable="true">focus me, then press the UP key</editor>
+<script>
+function runTest() {
+ document.body.offsetHeight;
+ var e = document.querySelector('editor');
+ e.focus()
+}
+</script>
diff --git a/layout/base/tests/bug1359411.html b/layout/base/tests/bug1359411.html
new file mode 100644
index 0000000000..9f166dd43b
--- /dev/null
+++ b/layout/base/tests/bug1359411.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html><head><script src="/tests/SimpleTest/EventUtils.js"></script><style>
+ i:before{content:"X"}
+</style></head><body onload="runTest()"><div><button type="button"><i></i></button><p></p></div><editor contenteditable="true" onfocus="sendKey('UP')">focus me, then press the UP key</editor>
+<script>var e = document.querySelector('editor'); e.focus()</script>
+<script>
+function runTest() {
+ document.body.offsetHeight;
+ var e = document.querySelector('editor');
+ e.focus()
+}
+</script>
diff --git a/layout/base/tests/bug1415416-ref.html b/layout/base/tests/bug1415416-ref.html
new file mode 100644
index 0000000000..df833ed7bf
--- /dev/null
+++ b/layout/base/tests/bug1415416-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1415416
+-->
+<head>
+<title>Test for Bug 1415416</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function runTest()
+{
+ sendKey("TAB");
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="runTest()">
+<input id = "text1" type="text" autofocus value=""/>
+<input id = "text2" type="text" value=""/>
+</body>
+</html>
diff --git a/layout/base/tests/bug1415416.html b/layout/base/tests/bug1415416.html
new file mode 100644
index 0000000000..e25394dc69
--- /dev/null
+++ b/layout/base/tests/bug1415416.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1415416
+-->
+<head>
+<title>Test for Bug 1415416</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function runTest() {
+ var text = document.getElementById("text1");
+ text.readOnly = false;
+ text.value = "";
+
+ text = document.getElementById("text2");
+ text.readOnly = false;
+ text.value = "";
+
+ sendKey("TAB");
+ document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="runTest()">
+<input id = "text1" type="text" readonly autofocus value="A" />
+<input id = "text2" type="text" readonly value="B"/>
+</body>
+</html>
diff --git a/layout/base/tests/bug1423331-1-ref.html b/layout/base/tests/bug1423331-1-ref.html
new file mode 100644
index 0000000000..bc8251091b
--- /dev/null
+++ b/layout/base/tests/bug1423331-1-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test reference for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+#editable::before {
+ content: "Write here";
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.getElementById("editable").focus();
+ document.documentElement.className = "";
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1423331-1.html b/layout/base/tests/bug1423331-1.html
new file mode 100644
index 0000000000..354d7740cc
--- /dev/null
+++ b/layout/base/tests/bug1423331-1.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+#editable::before {
+ content: "Write here";
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ SimpleTest.executeSoon(() => {
+ let div = document.getElementById("editable");
+ synthesizeMouseAtCenter(div, {});
+ synthesizeMouseAtCenter(div, {});
+ document.documentElement.className ="";
+ });
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1423331-2-ref.html b/layout/base/tests/bug1423331-2-ref.html
new file mode 100644
index 0000000000..60936c48a3
--- /dev/null
+++ b/layout/base/tests/bug1423331-2-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test reference for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ let div = document.getElementById("editable");
+ div.focus();
+ sendString("xxx");
+ document.documentElement.className = "";
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1423331-2.html b/layout/base/tests/bug1423331-2.html
new file mode 100644
index 0000000000..9615a29bd4
--- /dev/null
+++ b/layout/base/tests/bug1423331-2.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+#editable:empty::before {
+ content: "Write here";
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ SimpleTest.executeSoon(() => {
+ let div = document.getElementById("editable");
+ synthesizeMouseAtCenter(div, {});
+ synthesizeMouseAtCenter(div, {});
+ sendString("xxx");
+ document.documentElement.className = "";
+ });
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1423331-3.html b/layout/base/tests/bug1423331-3.html
new file mode 100644
index 0000000000..5dc6df8060
--- /dev/null
+++ b/layout/base/tests/bug1423331-3.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+#editable::before {
+ content: "Write here";
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ let div = document.getElementById("editable");
+ synthesizeMouse(div, 2, 2, {});
+ synthesizeMouse(div, 2, 2, {});
+ document.documentElement.className = "";
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1423331-4.html b/layout/base/tests/bug1423331-4.html
new file mode 100644
index 0000000000..c1a61d41c6
--- /dev/null
+++ b/layout/base/tests/bug1423331-4.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1423331: Contenteditable insertion with pseudo-elements</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+#editable {
+ outline: 1px solid black;
+ width: 300px;
+ height: 100px;
+}
+#editable:empty::before {
+ content: "Write here";
+}
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ let div = document.getElementById("editable");
+ synthesizeMouse(div, 2, 2, {});
+ synthesizeMouse(div, 2, 2, {});
+ sendString("xxx");
+ document.documentElement.className = "";
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1448730.html b/layout/base/tests/bug1448730.html
new file mode 100644
index 0000000000..7edbd7b0f7
--- /dev/null
+++ b/layout/base/tests/bug1448730.html
@@ -0,0 +1,103 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<p id="display">
+ <input id="input">
+ <textarea id="textarea" cols="10" rows="2"></textarea>
+ <div id="editable" contenteditable style="width: 200px;"></div>
+</p>
+<div id="content" style="display: none;"></div>
+<pre id="test">
+</pre>
+<script>
+const SimpleTest = parent.SimpleTest;
+const is = parent.is;
+const isnot = parent.isnot;
+const ok = parent.ok;
+
+window.addEventListener("load", runTest);
+
+function runTest() {
+ let target = document.getElementById("input");
+ target.focus();
+
+ // <input>
+ target.value = " ";
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ is(target.selectionStart, target.value.length, "Don't select whitespace");
+ is(target.selectionEnd, target.value.length, "Don't select whitespace");
+
+ target.value = "abc";
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ is(target.selectionStart, target.value.length, "Don't select word");
+ is(target.selectionEnd, target.value.length, "Don't select word");
+
+ target.value = " ".repeat(100);
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ is(target.selectionStart, 0, "Select whitespace");
+
+ // <textarea>
+ target = document.getElementById("textarea");
+ target.value = " ";
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ is(target.selectionStart, 2, "Don't select whitespace");
+ is(target.selectionEnd, 2, "Don't select whitespace");
+
+ target.value = "abc";
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ is(target.selectionStart, target.value.length, "Don't select word");
+ is(target.selectionEnd, target.value.length, "Don't select word");
+
+ target.value = " ".repeat(10) + "\n" + " ".repeat(10) + "\n" + " ".repeat(10);
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ isnot(target.selectionStart, target.selectionEnd, "Select whitespace");
+
+ // contenteditable
+ target = document.getElementById("editable");
+ target.innerHTML = "test";
+ target.focus();
+ let range = document.createRange();
+ range.setStart(target.firstChild, target.firstChild.length);
+ range.setEnd(target.firstChild, target.firstChild.length);
+ let selection = window.getSelection();
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ ok(selection.getRangeAt(0).collapsed, "Don't select word");
+
+ target.innerHTML = "t".repeat(50);
+ target.focus();
+ range = document.createRange();
+ range.setStart(target.firstChild, target.firstChild.length);
+ range.setEnd(target.firstChild, target.firstChild.length);
+ selection.removeAllRanges();
+ selection.addRange(range);
+
+ synthesizeTouchAtCenter(target, { type: "touchstart" });
+ synthesizeMouseAtCenter(target, { type: "mouselongtap" });
+ synthesizeTouchAtCenter(target, { type: "touchend" });
+ ok(!selection.getRangeAt(0).collapsed, "Select word");
+
+ SimpleTest.finish();
+}
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1484094-1-ref.html b/layout/base/tests/bug1484094-1-ref.html
new file mode 100644
index 0000000000..c319fa73a4
--- /dev/null
+++ b/layout/base/tests/bug1484094-1-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript">
+function start()
+{
+ var input = document.querySelector("input");
+ input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+ input.addEventListener("focus", () => {
+ input.setSelectionRange(input.value.length, input.value.length);
+ document.documentElement.removeAttribute("class");
+ });
+ input.focus();
+}
+ </script>
+</head>
+<body onload="start()">
+ <input type="text"></input>
+</body>
+</html>
diff --git a/layout/base/tests/bug1484094-1.html b/layout/base/tests/bug1484094-1.html
new file mode 100644
index 0000000000..7a41c8170b
--- /dev/null
+++ b/layout/base/tests/bug1484094-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript">
+function start()
+{
+ var input = document.querySelector("input");
+ input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+ input.addEventListener("focus", () => {
+ input.setSelectionRange(0, 0);
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+ input.focus();
+}
+ </script>
+</head>
+<body onload="start()">
+ <input type="text"></input>
+</body>
+</html>
diff --git a/layout/base/tests/bug1484094-2-ref.html b/layout/base/tests/bug1484094-2-ref.html
new file mode 100644
index 0000000000..3fa9ebf91c
--- /dev/null
+++ b/layout/base/tests/bug1484094-2-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="utf-8">
+ <script type="text/javascript">
+function start()
+{
+ var input = document.querySelector("input");
+ input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+ input.addEventListener("focus", () => {
+ input.setSelectionRange(0, 0);
+ document.documentElement.removeAttribute("class");
+ });
+ input.focus();
+}
+ </script>
+</head>
+<body onload="start()">
+ <input type="text"></input>
+</body>
+</html>
diff --git a/layout/base/tests/bug1484094-2.html b/layout/base/tests/bug1484094-2.html
new file mode 100644
index 0000000000..d5c3f92e5d
--- /dev/null
+++ b/layout/base/tests/bug1484094-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<head>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript">
+function start()
+{
+ var input = document.querySelector("input");
+ input.value = '\u{1f468}\u{200d}\u{1f469}\u{200d}\u{1f467}\u{200d}\u{1f466}';
+ input.addEventListener("focus", () => {
+ input.setSelectionRange(input.value.length, input.value.length);
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ });
+ input.focus();
+}
+ </script>
+</head>
+<body onload="start()">
+ <input type="text"></input>
+</body>
+</html>
diff --git a/layout/base/tests/bug1496118-ref.html b/layout/base/tests/bug1496118-ref.html
new file mode 100644
index 0000000000..af5026f985
--- /dev/null
+++ b/layout/base/tests/bug1496118-ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ input, input:active { border: none; }
+ </style>
+</head>
+<body>
+<input id="input1" value="aaaaaaaaaaaaaaaaaaaa">
+<div id=div1 style="height: 100px;">
+ <textarea id="textarea1" style="display: none;"></textarea>
+</div>
+<script>
+SimpleTest.waitForFocus(() => {
+ let input1 = document.getElementById('input1');
+ input1.focus();
+ input1.selectionStart = input1.selectionEnd = 0;
+ synthesizeKey("A");
+ document.documentElement.removeAttribute("class");
+});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1496118.html b/layout/base/tests/bug1496118.html
new file mode 100644
index 0000000000..cdfaf5c586
--- /dev/null
+++ b/layout/base/tests/bug1496118.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ input, input:active { border: none; }
+ </style>
+</head>
+<body>
+<input id="input1" value="aaaaaaaaaaaaaaaaaaaa">
+<div id=div1 style="height: 100px;">
+ <textarea id="textarea1"></textarea>
+</div>
+<script>
+SimpleTest.waitForFocus(() => {
+ let div1 = document.getElementById('div1');
+ let textarea1 = document.getElementById('textarea1');
+ div1.addEventListener("drop", e => {
+ e.preventDefault();
+
+ textarea1.style = "display: none;";
+ SimpleTest.executeSoon(() => {
+ synthesizeKey("A");
+ document.documentElement.removeAttribute("class");
+ });
+ });
+
+ let input1 = document.getElementById('input1');
+ input1.focus();
+ input1.setSelectionRange(0, input1.value.length);
+
+ synthesizeDrop(input1, textarea1, [[{type: "text/plain", data: "foo"}]]);
+});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug1506547-1.html b/layout/base/tests/bug1506547-1.html
new file mode 100644
index 0000000000..94cc07c2bf
--- /dev/null
+++ b/layout/base/tests/bug1506547-1.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Moving the caret in an editor jumps over non-editable nodes.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true">
+ I am div number one
+ <div contenteditable="false">X X X</div>
+ However I am editable
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ const noneditable = document.querySelector('div[contenteditable="false"]');
+ editable.focus();
+ synthesizeKey("KEY_ArrowDown");
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1506547-2.html b/layout/base/tests/bug1506547-2.html
new file mode 100644
index 0000000000..b9ccda446c
--- /dev/null
+++ b/layout/base/tests/bug1506547-2.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Moving the caret in an editor jumps over non-editable nodes.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true">
+ I am div number one
+ <div contenteditable="false">X X X</div>
+ However I am editable
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ // 5 words in the first line, plus the non-editable node.
+ for (let i = 0; i < "I am div number one".length + 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1506547-3.html b/layout/base/tests/bug1506547-3.html
new file mode 100644
index 0000000000..788670db50
--- /dev/null
+++ b/layout/base/tests/bug1506547-3.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Moving the caret in an editor jumps over non-editable nodes.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true">
+ I am div number one
+ <div contenteditable="false">X X X</div>
+ However I am editable
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ const noneditable = document.querySelector('div[contenteditable="false"]');
+ editable.focus();
+ synthesizeKey("KEY_End");
+ synthesizeKey("KEY_ArrowDown");
+ for (let i = 0; i < 4; ++i)
+ synthesizeKey("KEY_ArrowLeft", { ctrlKey: true });
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1506547-4-ref.html b/layout/base/tests/bug1506547-4-ref.html
new file mode 100644
index 0000000000..b013eed7f5
--- /dev/null
+++ b/layout/base/tests/bug1506547-4-ref.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>Caret on editable line with non-editable content and whitespace.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true"><span>xyz </span><br>editable</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouse(editable, 100, 10, {});
+});
+</script>
diff --git a/layout/base/tests/bug1506547-4.html b/layout/base/tests/bug1506547-4.html
new file mode 100644
index 0000000000..7e83de99a9
--- /dev/null
+++ b/layout/base/tests/bug1506547-4.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<title>Caret on editable line with non-editable content and whitespace.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true"><span contenteditable="false">xyz </span><br>editable</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouse(editable, 100, 10, {});
+});
+</script>
diff --git a/layout/base/tests/bug1506547-5-ref.html b/layout/base/tests/bug1506547-5-ref.html
new file mode 100644
index 0000000000..6c0adc967f
--- /dev/null
+++ b/layout/base/tests/bug1506547-5-ref.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret on editable line with non-editable content and whitespace.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true"><span>xyz </span><br>editable</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouse(editable, 100, 10, {});
+ setTimeout(() => {
+ sendString("xxx");
+ setTimeout(() => document.documentElement.className = "");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1506547-5.html b/layout/base/tests/bug1506547-5.html
new file mode 100644
index 0000000000..f06e72fbe2
--- /dev/null
+++ b/layout/base/tests/bug1506547-5.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret on editable line with non-editable content and whitespace.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true"><span contenteditable="false">xyz </span><br>editable</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouse(editable, 100, 10, {});
+ setTimeout(() => {
+ sendString("xxx");
+ setTimeout(() => document.documentElement.className = "");
+ });
+});
+</script>
+</html>
diff --git a/layout/base/tests/bug1506547-6.html b/layout/base/tests/bug1506547-6.html
new file mode 100644
index 0000000000..d6a780e48a
--- /dev/null
+++ b/layout/base/tests/bug1506547-6.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret on editable line with non-editable content and whitespace.</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ * { outline: none }
+
+ div {
+ border: 1px solid red;
+ margin: 5px;
+ padding: 2px;
+ }
+</style>
+<div contenteditable="true"><span contenteditable="false">xyz </span><br>editable</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowLeft");
+ setTimeout(() => {
+ sendString("xxx");
+ setTimeout(() => document.documentElement.className = "");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1510942-1-ref.html b/layout/base/tests/bug1510942-1-ref.html
new file mode 100644
index 0000000000..2bafe4cc63
--- /dev/null
+++ b/layout/base/tests/bug1510942-1-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ #editable {
+ height: 300px;
+ width: 100px;
+ outline: 1px solid black;
+ }
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.getElementById("editable").focus();
+ document.documentElement.className ="";
+});
+</script>
diff --git a/layout/base/tests/bug1510942-1.html b/layout/base/tests/bug1510942-1.html
new file mode 100644
index 0000000000..35f90ef75a
--- /dev/null
+++ b/layout/base/tests/bug1510942-1.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1510942: Caret in a transformed empty block</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ #editable {
+ height: 300px;
+ width: 100px;
+ outline: 1px solid black;
+ transform: translateZ(0);
+ }
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.getElementById("editable").focus();
+ document.documentElement.className ="";
+});
+</script>
diff --git a/layout/base/tests/bug1510942-2-ref.html b/layout/base/tests/bug1510942-2-ref.html
new file mode 100644
index 0000000000..11185e8f0c
--- /dev/null
+++ b/layout/base/tests/bug1510942-2-ref.html
@@ -0,0 +1,4 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test reference</title>
+<!-- intentionally blank -->
diff --git a/layout/base/tests/bug1510942-2.html b/layout/base/tests/bug1510942-2.html
new file mode 100644
index 0000000000..63f067bf30
--- /dev/null
+++ b/layout/base/tests/bug1510942-2.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Test for bug 1510942: Caret in a transformed empty block</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ #editable {
+ height: 300px;
+ width: 100px;
+ outline: 1px solid black;
+ opacity: 0;
+ }
+</style>
+<div id="editable" contenteditable></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.getElementById("editable").focus();
+ document.documentElement.className ="";
+});
+</script>
diff --git a/layout/base/tests/bug1516963-1-ref.html b/layout/base/tests/bug1516963-1-ref.html
new file mode 100644
index 0000000000..2e09115cc6
--- /dev/null
+++ b/layout/base/tests/bug1516963-1-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on an empty grid container.</title>
+ <style>
+ #container {
+ display: grid;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-1.html b/layout/base/tests/bug1516963-1.html
new file mode 100644
index 0000000000..4731472c13
--- /dev/null
+++ b/layout/base/tests/bug1516963-1.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty grid container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: grid;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-2-ref.html b/layout/base/tests/bug1516963-2-ref.html
new file mode 100644
index 0000000000..eb5f393e8b
--- /dev/null
+++ b/layout/base/tests/bug1516963-2-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on an empty flex container.</title>
+ <style>
+ #container {
+ display: flex;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-2.html b/layout/base/tests/bug1516963-2.html
new file mode 100644
index 0000000000..a546f7955f
--- /dev/null
+++ b/layout/base/tests/bug1516963-2.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty flex container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: flex;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-3-ref.html b/layout/base/tests/bug1516963-3-ref.html
new file mode 100644
index 0000000000..4cb022c0e4
--- /dev/null
+++ b/layout/base/tests/bug1516963-3-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on a grid container.</title>
+ <style>
+ #container {
+ display: grid;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <body>
+ <div id="container"><button>Grid</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-3.html b/layout/base/tests/bug1516963-3.html
new file mode 100644
index 0000000000..9ae289ae27
--- /dev/null
+++ b/layout/base/tests/bug1516963-3.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on a grid container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: grid;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><button>Grid</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-4-ref.html b/layout/base/tests/bug1516963-4-ref.html
new file mode 100644
index 0000000000..f069588d18
--- /dev/null
+++ b/layout/base/tests/bug1516963-4-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on a flex container.</title>
+ <style>
+ #container {
+ display: flex;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <body>
+ <div id="container"><button>Flex</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-4.html b/layout/base/tests/bug1516963-4.html
new file mode 100644
index 0000000000..c1bea13352
--- /dev/null
+++ b/layout/base/tests/bug1516963-4.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on a flex container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: flex;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><button>Flex</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-5-ref.html b/layout/base/tests/bug1516963-5-ref.html
new file mode 100644
index 0000000000..2814863de2
--- /dev/null
+++ b/layout/base/tests/bug1516963-5-ref.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on an empty table container.</title>
+ <style>
+ #container {
+ display: table;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-5.html b/layout/base/tests/bug1516963-5.html
new file mode 100644
index 0000000000..77510d0118
--- /dev/null
+++ b/layout/base/tests/bug1516963-5.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty table container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: table;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-6-ref.html b/layout/base/tests/bug1516963-6-ref.html
new file mode 100644
index 0000000000..c30483ce57
--- /dev/null
+++ b/layout/base/tests/bug1516963-6-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on a table container.</title>
+ <style>
+ #container {
+ display: table;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <body>
+ <div id="container"><button>Table</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1516963-6.html b/layout/base/tests/bug1516963-6.html
new file mode 100644
index 0000000000..e9add6924b
--- /dev/null
+++ b/layout/base/tests/bug1516963-6.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on a table container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: table;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ button {
+ height: 40px;
+ width: 60px;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><button>Table</button></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1518339-1-ref.html b/layout/base/tests/bug1518339-1-ref.html
new file mode 100644
index 0000000000..7f479aaec8
--- /dev/null
+++ b/layout/base/tests/bug1518339-1-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div contenteditable="true">
+ Can you edit <b>me</b>?
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouseAtCenter(editable.querySelector("b"), {});
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
diff --git a/layout/base/tests/bug1518339-1.html b/layout/base/tests/bug1518339-1.html
new file mode 100644
index 0000000000..72444a26f8
--- /dev/null
+++ b/layout/base/tests/bug1518339-1.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>contenteditable is selectable by default, even with a user-select: none ancestor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ :root {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ user-select: none;
+ }
+</style>
+<div contenteditable="true">
+ Can you edit <b>me</b>?
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouseAtCenter(editable.querySelector("b"), {});
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
diff --git a/layout/base/tests/bug1518339-2-ref.html b/layout/base/tests/bug1518339-2-ref.html
new file mode 100644
index 0000000000..108962bc79
--- /dev/null
+++ b/layout/base/tests/bug1518339-2-ref.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div {
+ -webkit-user-select: all;
+ -moz-user-select: all;
+ user-select: all;
+ }
+</style>
+<div contenteditable="true">
+ <div>
+ You should be able to select <b>all</b> of me.
+ </div>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouseAtCenter(editable.querySelector("b"), {});
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
diff --git a/layout/base/tests/bug1518339-2.html b/layout/base/tests/bug1518339-2.html
new file mode 100644
index 0000000000..0b62b12680
--- /dev/null
+++ b/layout/base/tests/bug1518339-2.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>user-select can be overriden on a contenteditable element</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div {
+ -webkit-user-select: all;
+ -moz-user-select: all;
+ user-select: all;
+ }
+</style>
+<div contenteditable="true">
+ You should be able to select <b>all</b> of me.
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ const editable = document.querySelector('div[contenteditable="true"]');
+ editable.focus();
+ synthesizeMouseAtCenter(editable.querySelector("b"), {});
+ setTimeout(() => document.documentElement.removeAttribute("class"), 0);
+});
+</script>
diff --git a/layout/base/tests/bug1524266-1-ref.html b/layout/base/tests/bug1524266-1-ref.html
new file mode 100644
index 0000000000..be5cc8ff9b
--- /dev/null
+++ b/layout/base/tests/bug1524266-1-ref.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret doesn't get stuck in a select element inside an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus-within {
+ outline: 3px solid blue;
+ }
+ span {
+ outline: none;
+ }
+</style>
+<div>
+ xx
+ <select>
+ <option value="">Placeholder</option>
+ </select>
+ <span contenteditable="true" spellcheck="false">xxx</span>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1524266-1.html b/layout/base/tests/bug1524266-1.html
new file mode 100644
index 0000000000..4a011e6892
--- /dev/null
+++ b/layout/base/tests/bug1524266-1.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret doesn't get stuck in a select element inside an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx
+ <select>
+ <option value="">Placeholder</option>
+ </select>
+ xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ for (let i = 0; i < 7; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1524266-2-ref.html b/layout/base/tests/bug1524266-2-ref.html
new file mode 100644
index 0000000000..99024c4b83
--- /dev/null
+++ b/layout/base/tests/bug1524266-2-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Can delete non-selectable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xxxxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1524266-2.html b/layout/base/tests/bug1524266-2.html
new file mode 100644
index 0000000000..459bd12cb5
--- /dev/null
+++ b/layout/base/tests/bug1524266-2.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Can delete non-selectable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ /* <select> has user-select: none in the UA sheet, but just in case */
+ select {
+ -moz-user-select: none;
+ user-select: none;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx
+ <select>
+ <option value="">Placeholder</option>
+ </select>
+ xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ // Select whitespace + <select> + whitespace.
+ for (let i = 0; i < 3; ++i)
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ // Rip it off.
+ synthesizeKey("KEY_Delete");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1524266-3.html b/layout/base/tests/bug1524266-3.html
new file mode 100644
index 0000000000..39708d00ba
--- /dev/null
+++ b/layout/base/tests/bug1524266-3.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Can delete non-selectable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ span {
+ -moz-user-select: none;
+ user-select: none;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx
+ <span>
+ NOT EDITABLE
+ </span>
+ xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ // Select whitespace + <span>
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ // Rip it off.
+ synthesizeKey("KEY_Delete");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1524266-4.html b/layout/base/tests/bug1524266-4.html
new file mode 100644
index 0000000000..1b0eb35a07
--- /dev/null
+++ b/layout/base/tests/bug1524266-4.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Can delete non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx
+ <span contenteditable="false">NOT EDITABLE</span>xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ // Select whitespace + <span>
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ // Rip it off.
+ synthesizeKey("KEY_Delete");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1529492-1-ref.html b/layout/base/tests/bug1529492-1-ref.html
new file mode 100644
index 0000000000..ae643b2b92
--- /dev/null
+++ b/layout/base/tests/bug1529492-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.parent {
+ font: 32px monospace;
+ width: 26ch;
+ transform: scaleX(0.5);
+ transform-origin: 0 0;
+}
+#child {
+ outline: none;
+}
+</style>
+<div class="parent"><div id="child">.BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz</div></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ let r = document.createRange();
+ let t = child.firstChild;
+ r.setStart(t, 30);
+ r.setEnd(t, 30);
+
+ let sel = getSelection();
+ sel.empty();
+ sel.addRange(r);
+
+ document.documentElement.removeAttribute("class");
+});
+</script>
diff --git a/layout/base/tests/bug1529492-1.html b/layout/base/tests/bug1529492-1.html
new file mode 100644
index 0000000000..f01e769dd0
--- /dev/null
+++ b/layout/base/tests/bug1529492-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<!-- This test relies on caret browsing being enabled. -->
+<html class="reftest-wait">
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+.parent {
+ font: 32px monospace;
+ width: 26ch;
+ overflow: hidden;
+}
+#child {
+ transform: scaleX(0.5);
+ transform-origin: 0 0;
+ outline: none;
+}
+</style>
+<div class="parent"><div id="child">.BCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz</div></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ let r = document.createRange();
+ let t = child.firstChild;
+
+ // With a scale of 0.5, the bug manifests by limiting the caret's position
+ // to half way through the text. Place the selection somewhere past that.
+ r.setStart(t, 30);
+ r.setEnd(t, 30);
+
+ let sel = getSelection();
+ sel.empty();
+ sel.addRange(r);
+
+ document.documentElement.removeAttribute("class");
+});
+</script>
diff --git a/layout/base/tests/bug1550869-1-ref.html b/layout/base/tests/bug1550869-1-ref.html
new file mode 100644
index 0000000000..9a7f0712d6
--- /dev/null
+++ b/layout/base/tests/bug1550869-1-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on an image.</title>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ img {
+ width: 60px;
+ height: 40px;
+ }
+ </style>
+ <body>
+ <div id="container"><img src="chrome/blue-32x32.png"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-1a.html b/layout/base/tests/bug1550869-1a.html
new file mode 100644
index 0000000000..d19de01d5c
--- /dev/null
+++ b/layout/base/tests/bug1550869-1a.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on a draggable image.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ img {
+ width: 60px;
+ height: 40px;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let img = document.getElementById("img");
+ synthesizeMouseAtCenter(img, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><img id="img" src="chrome/blue-32x32.png"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-1b.html b/layout/base/tests/bug1550869-1b.html
new file mode 100644
index 0000000000..c1cd7fa70e
--- /dev/null
+++ b/layout/base/tests/bug1550869-1b.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an undraggable image.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ img {
+ width: 60px;
+ height: 40px;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let img = document.getElementById("img");
+ synthesizeMouseAtCenter(img, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><img id="img" src="chrome/blue-32x32.png" draggable="false"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-1c.html b/layout/base/tests/bug1550869-1c.html
new file mode 100644
index 0000000000..b18c835616
--- /dev/null
+++ b/layout/base/tests/bug1550869-1c.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an undraggable image button.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ input {
+ border: 0;
+ outline: 0;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let img = document.getElementById("img");
+ synthesizeMouseAtCenter(img, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"><input draggable="false" width="60px" height="40px" type="image" src="chrome/blue-32x32.png" id="img"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-2-ref.html b/layout/base/tests/bug1550869-2-ref.html
new file mode 100644
index 0000000000..e4215edebc
--- /dev/null
+++ b/layout/base/tests/bug1550869-2-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963 Reference: Test AccessibleCaret doesn't show when clicking on an empty container.</title>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <body>
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-2a.html b/layout/base/tests/bug1550869-2a.html
new file mode 100644
index 0000000000..6cf97bf443
--- /dev/null
+++ b/layout/base/tests/bug1550869-2a.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty inline-grid container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: inline-grid;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-2b.html b/layout/base/tests/bug1550869-2b.html
new file mode 100644
index 0000000000..d6140a11a5
--- /dev/null
+++ b/layout/base/tests/bug1550869-2b.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty inline-flex container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: inline-flex;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-2c.html b/layout/base/tests/bug1550869-2c.html
new file mode 100644
index 0000000000..97de29305c
--- /dev/null
+++ b/layout/base/tests/bug1550869-2c.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty inline-table container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ display: inline-table;
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <div id="container"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1550869-2d.html b/layout/base/tests/bug1550869-2d.html
new file mode 100644
index 0000000000..7515078a81
--- /dev/null
+++ b/layout/base/tests/bug1550869-2d.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty svg container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ outline: 0;
+ }
+ </style>
+ <script>
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => { document.documentElement.removeAttribute("class"); });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <svg id="container"></svg>
+ </body>
+</html>
diff --git a/layout/base/tests/bug1591282-1-ref.html b/layout/base/tests/bug1591282-1-ref.html
new file mode 100644
index 0000000000..ad1cbec754
--- /dev/null
+++ b/layout/base/tests/bug1591282-1-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret doesn't get occluded by background-color of inline child</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<span>xxx</span>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1591282-1.html b/layout/base/tests/bug1591282-1.html
new file mode 100644
index 0000000000..8e5e0b3f16
--- /dev/null
+++ b/layout/base/tests/bug1591282-1.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret doesn't get occluded by background-color of inline child</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ span {
+ /* This is the point of the test */
+ background-color: white
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<span>xxx</span>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ for (let i = 0; i < 2; ++i)
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1611661-ref.html b/layout/base/tests/bug1611661-ref.html
new file mode 100644
index 0000000000..5c9d2d674e
--- /dev/null
+++ b/layout/base/tests/bug1611661-ref.html
@@ -0,0 +1,18 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div>
+ <input type="number" value="33">
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ requestAnimationFrame(function() {
+ // Move after the 3
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.className = "";
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1611661.html b/layout/base/tests/bug1611661.html
new file mode 100644
index 0000000000..d92ae05e66
--- /dev/null
+++ b/layout/base/tests/bug1611661.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Can edit input type=number with a user-select: none ancestor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<div style="user-select: none">
+ <input type="number" value="322223">
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ requestAnimationFrame(function() {
+ // Move after the 3
+ synthesizeKey("KEY_ArrowRight");
+ // Select "2222"
+ for (let i = 0; i < 4; ++i)
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ // Rip it off.
+ synthesizeKey("KEY_Delete");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634543-1-ref.html b/layout/base/tests/bug1634543-1-ref.html
new file mode 100644
index 0000000000..a65a6b4a31
--- /dev/null
+++ b/layout/base/tests/bug1634543-1-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Reference</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false"><br></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634543-1.html b/layout/base/tests/bug1634543-1.html
new file mode 100644
index 0000000000..039d93df64
--- /dev/null
+++ b/layout/base/tests/bug1634543-1.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly position for block whose only child is an abspos pseudo</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ div::before {
+ content: "";
+ /* This is the point of the test */
+ position: absolute;
+ }
+</style>
+<div contenteditable="true" spellcheck="false"></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634543-2.html b/layout/base/tests/bug1634543-2.html
new file mode 100644
index 0000000000..fb05a4e4bb
--- /dev/null
+++ b/layout/base/tests/bug1634543-2.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly position for block whose only child is an abspos span</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ span {
+ /* This is the point of the test */
+ position: absolute;
+ }
+</style>
+<div contenteditable="true" spellcheck="false"><span></span></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634543-3.html b/layout/base/tests/bug1634543-3.html
new file mode 100644
index 0000000000..547c1c3559
--- /dev/null
+++ b/layout/base/tests/bug1634543-3.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly position for block whose only child is an empty span</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false"><span></span></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634543-4.html b/layout/base/tests/bug1634543-4.html
new file mode 100644
index 0000000000..4f82ea65b8
--- /dev/null
+++ b/layout/base/tests/bug1634543-4.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly position for block whose only child is an empty pseudo</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+ div::before {
+ content: "";
+ }
+</style>
+<div contenteditable="true" spellcheck="false"></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634743-1-ref.html b/layout/base/tests/bug1634743-1-ref.html
new file mode 100644
index 0000000000..55d0366359
--- /dev/null
+++ b/layout/base/tests/bug1634743-1-ref.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Test reference</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div {
+ outline: 2px solid blue;
+ line-height: 5;
+ border: 1px solid gray;
+ padding: 8px;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">abc</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1634743-1.html b/layout/base/tests/bug1634743-1.html
new file mode 100644
index 0000000000..1c41b2a012
--- /dev/null
+++ b/layout/base/tests/bug1634743-1.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly position for block whose only child is a non-empty pseudo, if line-height is used</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 2px solid blue;
+ }
+
+ div {
+ line-height: 5;
+ border: 1px solid gray;
+ padding: 8px;
+ }
+ div::before {
+ content: "abc";
+ }
+</style>
+<div contenteditable="true" spellcheck="false"></div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1637476-1-ref.html b/layout/base/tests/bug1637476-1-ref.html
new file mode 100644
index 0000000000..a56137aca9
--- /dev/null
+++ b/layout/base/tests/bug1637476-1-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Reference: Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1637476-1.html b/layout/base/tests/bug1637476-1.html
new file mode 100644
index 0000000000..7bc32fa031
--- /dev/null
+++ b/layout/base/tests/bug1637476-1.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input::placeholder {
+ background: white;
+ opacity: 1;
+}
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input placeholder="&nbsp;">
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ setTimeout(function(){document.documentElement.removeAttribute("class");}, 0);
+});
+</script>
diff --git a/layout/base/tests/bug1637476-2-ref.html b/layout/base/tests/bug1637476-2-ref.html
new file mode 100644
index 0000000000..da06bfafc2
--- /dev/null
+++ b/layout/base/tests/bug1637476-2-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Reference: Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input type=password>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1637476-2.html b/layout/base/tests/bug1637476-2.html
new file mode 100644
index 0000000000..3fb69f5dca
--- /dev/null
+++ b/layout/base/tests/bug1637476-2.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input::placeholder {
+ background: white;
+ opacity: 1;
+}
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input type=password placeholder="&nbsp;">
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ setTimeout(function(){document.documentElement.removeAttribute("class");}, 0);
+});
+</script>
diff --git a/layout/base/tests/bug1637476-3-ref.html b/layout/base/tests/bug1637476-3-ref.html
new file mode 100644
index 0000000000..ca4b9d97ef
--- /dev/null
+++ b/layout/base/tests/bug1637476-3-ref.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Reference: Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input type=number>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ requestAnimationFrame(function() {
+ requestAnimationFrame(function() {
+ document.documentElement.removeAttribute("class");
+ });
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1637476-3.html b/layout/base/tests/bug1637476-3.html
new file mode 100644
index 0000000000..a133a07f2e
--- /dev/null
+++ b/layout/base/tests/bug1637476-3.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted with placeholder opacity:1</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+input::placeholder {
+ background: white;
+ opacity: 1;
+}
+input {
+ -webkit-appearance:none; /* to avoid bug 1637804 */
+ /* reset any UA styles and use colors that won't trigger anti-aliasing issues */
+ background: white;
+ border: 1px solid black;
+ outline: 1px solid black;
+}
+</style>
+<input type=number placeholder="&nbsp;">
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('input').focus();
+ setTimeout(function(){document.documentElement.removeAttribute("class");}, 0);
+});
+</script>
diff --git a/layout/base/tests/bug1663475-1-ref.html b/layout/base/tests/bug1663475-1-ref.html
new file mode 100644
index 0000000000..180a5aa7ce
--- /dev/null
+++ b/layout/base/tests/bug1663475-1-ref.html
@@ -0,0 +1,28 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted over inline with clip</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ div {
+ font: 16px/1 monospace;
+ outline: 2px solid blue;
+ caret-color: black;
+ }
+ span {
+ color: transparent;
+ }
+</style>
+<div contenteditable spellcheck="false">
+ <span>ab</span>c<span>de</span>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable]').focus();
+ requestAnimationFrame(function() {
+ // Position the caret between "a" and "b".
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1663475-1.html b/layout/base/tests/bug1663475-1.html
new file mode 100644
index 0000000000..5e046d8c80
--- /dev/null
+++ b/layout/base/tests/bug1663475-1.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted over inline with clip</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ div {
+ font: 16px/1 monospace;
+ caret-color: black;
+ }
+ div:focus {
+ outline: 2px solid blue;
+ }
+ span {
+ /* This should only leave the "c" letter visible, but the caret should
+ still be visible when between "a" and "b" */
+ clip-path: inset(0 2ch);
+ }
+</style>
+<div contenteditable spellcheck="false">
+ <span>abcde</span>
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable]').focus();
+ requestAnimationFrame(function() {
+ // Position the caret between "a" and "b".
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1663475-2-ref.html b/layout/base/tests/bug1663475-2-ref.html
new file mode 100644
index 0000000000..25584df128
--- /dev/null
+++ b/layout/base/tests/bug1663475-2-ref.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted over inline with clip</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ span[contenteditable] {
+ font: 16px/1 monospace;
+ caret-color: black;
+ display: inline-block;
+ outline: 2px solid blue;
+ }
+ span > span {
+ color: transparent;
+ }
+</style>
+<span contenteditable spellcheck="false">
+ <span>ab</span>c<span>de</span>
+</span>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable]').focus();
+ requestAnimationFrame(function() {
+ // Position the caret between "a" and "b".
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1663475-2.html b/layout/base/tests/bug1663475-2.html
new file mode 100644
index 0000000000..aa6ddf4bff
--- /dev/null
+++ b/layout/base/tests/bug1663475-2.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Caret is correctly painted over inline with clip</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ span[contenteditable] {
+ font: 16px/1 monospace;
+ caret-color: black;
+ display: inline-block;
+ }
+ span[contenteditable]:focus {
+ outline: 2px solid blue;
+ }
+ span > span {
+ /* This should only leave the "c" letter visible, but the caret should
+ still be visible when between "a" and "b" */
+ clip-path: inset(0 2ch);
+ }
+</style>
+<span contenteditable spellcheck="false">
+ <span>abcde</span>
+</span>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable]').focus();
+ requestAnimationFrame(function() {
+ // Position the caret between "a" and "b".
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1670531-1.html b/layout/base/tests/bug1670531-1.html
new file mode 100644
index 0000000000..42fc3de818
--- /dev/null
+++ b/layout/base/tests/bug1670531-1.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Select non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<span contenteditable="false">NOT EDITABLE</span>xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ // Select <span>
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1670531-2.html b/layout/base/tests/bug1670531-2.html
new file mode 100644
index 0000000000..7dfc05b06c
--- /dev/null
+++ b/layout/base/tests/bug1670531-2.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Select non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<span contenteditable="false">NOT EDITABLE</span>xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move before the three x
+ for (let i = 0; i < 3; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ // Select <span>
+ synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1670531-3-ref.html b/layout/base/tests/bug1670531-3-ref.html
new file mode 100644
index 0000000000..08aaf01f08
--- /dev/null
+++ b/layout/base/tests/bug1670531-3-ref.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Select non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<img src="image_rgrg-256x256.png">xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ // Select <img>
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1670531-3.html b/layout/base/tests/bug1670531-3.html
new file mode 100644
index 0000000000..aaec76ee4a
--- /dev/null
+++ b/layout/base/tests/bug1670531-3.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Select non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<img contenteditable="false" src="image_rgrg-256x256.png">xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move after the two x
+ for (let i = 0; i < 2; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ // Select <img>
+ synthesizeKey("KEY_ArrowRight", { shiftKey: true });
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug1670531-4.html b/layout/base/tests/bug1670531-4.html
new file mode 100644
index 0000000000..7b7786d232
--- /dev/null
+++ b/layout/base/tests/bug1670531-4.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<title>Select non-editable content in an editor</title>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div:focus {
+ outline: 3px solid blue;
+ }
+</style>
+<div contenteditable="true" spellcheck="false">
+ xx<img contenteditable="false" src="image_rgrg-256x256.png">xxx
+</div>
+<script>
+SimpleTest.waitForFocus(function() {
+ document.querySelector('[contenteditable="true"]').focus();
+ requestAnimationFrame(function() {
+ // Move before the three x
+ for (let i = 0; i < 3; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ // Select <img>
+ synthesizeKey("KEY_ArrowLeft", { shiftKey: true });
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug240933-1-ref.html b/layout/base/tests/bug240933-1-ref.html
new file mode 100644
index 0000000000..778a0647b8
--- /dev/null
+++ b/layout/base/tests/bug240933-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML><html>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none">
+
+</textarea>
+<script>
+ var t = document.getElementById("t");
+ t.selectionStart = t.selectionEnd = t.value.length;
+ t.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug240933-1.html b/layout/base/tests/bug240933-1.html
new file mode 100644
index 0000000000..771575aece
--- /dev/null
+++ b/layout/base/tests/bug240933-1.html
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RETURN'); // press Enter once
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug240933-2.html b/layout/base/tests/bug240933-2.html
new file mode 100644
index 0000000000..ea808f4d42
--- /dev/null
+++ b/layout/base/tests/bug240933-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ var area = document.getElementById('t');
+ area.focus();
+
+ sendKey('RETURN'); // press Enter twice
+ sendKey('RETURN');
+ sendKey('BACK_SPACE'); // press Backspace once
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug369950-subframe.xml b/layout/base/tests/bug369950-subframe.xml
new file mode 100644
index 0000000000..8aed64cd4e
--- /dev/null
+++ b/layout/base/tests/bug369950-subframe.xml
@@ -0,0 +1,11 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+
+<head>
+</head>
+
+<body>
+
+<p>foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo foo <x></y></p>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-1-ref.html b/layout/base/tests/bug389321-1-ref.html
new file mode 100644
index 0000000000..f8dd9c407d
--- /dev/null
+++ b/layout/base/tests/bug389321-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
+<script>
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Focus the span to put the caret at its beginning.
+ var area = document.getElementById('t');
+ area.focus();
+
+ // Do nothing else.
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-1.html b/layout/base/tests/bug389321-1.html
new file mode 100644
index 0000000000..f678865930
--- /dev/null
+++ b/layout/base/tests/bug389321-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable id="t" style="border: 1px dashed green; min-height: 2px; padding-right: 20px;"> </span></body>
+<script>
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+
+ // Focus the span to put the caret at its beginning.
+ var area = document.getElementById('t');
+ area.focus();
+
+ // Enter a character in the span then delete it.
+ sendChar("W");
+ sendKey("BACK_SPACE");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-2-ref.html b/layout/base/tests/bug389321-2-ref.html
new file mode 100644
index 0000000000..09adebf7e7
--- /dev/null
+++ b/layout/base/tests/bug389321-2-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-2.html b/layout/base/tests/bug389321-2.html
new file mode 100644
index 0000000000..d878635a1d
--- /dev/null
+++ b/layout/base/tests/bug389321-2.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 50px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-3-ref.html b/layout/base/tests/bug389321-3-ref.html
new file mode 100644
index 0000000000..387cbf25d2
--- /dev/null
+++ b/layout/base/tests/bug389321-3-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;">&nbsp;</div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug389321-3.html b/layout/base/tests/bug389321-3.html
new file mode 100644
index 0000000000..09adebf7e7
--- /dev/null
+++ b/layout/base/tests/bug389321-3.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML><html>
+<body>
+ <div contenteditable id="x" style="height: 30px; outline: none;"></div>
+<script>
+ var div = document.getElementById('x');
+ div.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug450930.xhtml b/layout/base/tests/bug450930.xhtml
new file mode 100644
index 0000000000..0981fff0a7
--- /dev/null
+++ b/layout/base/tests/bug450930.xhtml
@@ -0,0 +1,181 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450930
+-->
+<head>
+ <title>Test for Bug 450930 (MozAfterPaint)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="runNext()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=450930">Mozilla Bug 450930</a>
+<div id="display">
+ <div id="d" style="width:400px; height:200px;"></div>
+ <iframe id="iframe" style="width:400px; height:200px;"
+ src="data:text/html,&lt;div id='d'&gt;&lt;span style='margin-left:3px;'&gt;Hello&lt;/span&gt;
+ &lt;/div&gt;&lt;div style='margin-top:500px' id='d2'&gt;
+ &lt;span style='margin-left:3px;'&gt;Goodbye&lt;/span&gt;&lt;/div>"></iframe>
+ <svg:svg style="width:410px; height:210px;" id="svg">
+ <svg:foreignObject width="100%" height="100%">
+ <iframe id="iframe2" style="width:400px; height:200px;"
+ src="data:text/html,&lt;div id='d'&gt;&lt;span style='margin-left:3px;'&gt;Hello&lt;/span&gt;
+ &lt;/div&gt;&lt;div style='margin-top:500px' id='d2'&gt;
+ &lt;span style='margin-left:3px;'&gt;Goodbye&lt;/span&gt;&lt;/div>"></iframe>
+ </svg:foreignObject>
+ </svg:svg>
+</div>
+<div id="content" style="display: none">
+</div>
+
+
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+function flash(doc, name) {
+ var d = doc.getElementById(name);
+ d.style.backgroundColor = d.style.backgroundColor == "blue" ? "yellow" : "blue";
+ // Now flush out style changes in that document, since our event listeners
+ // seem to assume that things will work that way.
+ d.getBoundingClientRect();
+}
+
+function le(v1, v2, s) {
+ window.opener.ok(v1 <= v2, s + " (" + v1 + "," + v2 + ")");
+}
+
+function checkContains(r1, r2, s) {
+ le(Math.round(r1.left), Math.round(r2.left), "Left edges out" + s);
+ le(Math.round(r2.right), Math.round(r1.right), "Right edges out" + s);
+ le(Math.round(r1.top), Math.round(r2.top), "Top edges out" + s);
+ le(Math.round(r2.bottom), Math.round(r1.bottom), "Bottom edges out" + s);
+}
+
+function isRect(r1, r2) {
+ return (Math.abs(r1.left - r2.left) <= 1 ||
+ Math.abs(r1.right - r2.right) <= 1 ||
+ Math.abs(r1.top - r2.top) <= 1 ||
+ Math.abs(r1.bottom - r2.bottom) <= 1);
+}
+
+function isRectInList(r, list) {
+ for (var i = 0; i < list.length; ++i) {
+ if (isRect(r, list[i]))
+ return true;
+ }
+ return false;
+}
+
+function doesRectContain(r1, r2) {
+ return Math.floor(r1.left) <= r2.left && r2.right <= Math.ceil(r1.right) &&
+ Math.floor(r1.top) <= r2.top && r2.bottom <= Math.ceil(r1.bottom);
+}
+
+function rectToString(r) {
+ return "(" + r.left + "," + r.top + "," + r.right + "," + r.bottom + ")";
+}
+
+function doesRectContainListElement(r, list) {
+ dump("Incoming rect: " + rectToString(r) + "\n");
+ for (var i = 0; i < list.length; ++i) {
+ dump("List rect " + i + ": " + rectToString(list[i]));
+ if (doesRectContain(r, list[i])) {
+ dump(" FOUND\n");
+ return true;
+ }
+ dump("\n");
+ }
+ dump("NOT FOUND\n");
+ return false;
+}
+
+function checkGotSubdoc(list, container) {
+ var r = container.getBoundingClientRect();
+ return doesRectContainListElement(r, list);
+}
+
+function runTest1() {
+ // test basic functionality
+ var iterations = 0;
+ var foundExactRect = false;
+
+ function listener(event) {
+ var r = SpecialPowers.wrap(event).boundingClientRect;
+ var bounds = document.getElementById('d').getBoundingClientRect();
+ checkContains(r, bounds, "");
+ if (isRectInList(bounds, SpecialPowers.wrap(event).clientRects)) {
+ foundExactRect = true;
+ }
+ window.removeEventListener("MozAfterPaint", listener);
+ ++iterations;
+ if (iterations < 4) {
+ setTimeout(triggerPaint, 100);
+ } else {
+ window.opener.ok(foundExactRect, "Found exact rect");
+ runNext();
+ }
+ }
+
+ function triggerPaint() {
+ window.addEventListener("MozAfterPaint", listener);
+ flash(document, 'd');
+ window.opener.ok(true, "trigger test1 paint");
+ }
+ triggerPaint();
+}
+
+function runTest2(frameID, containerID) {
+ // test reporting of painting in subdocuments
+ var fired = 0;
+ var gotSubdocPrivileged = false;
+ var iframe = document.getElementById(frameID);
+ var container = document.getElementById(containerID);
+
+ function listener(event) {
+ if (checkGotSubdoc(SpecialPowers.wrap(event).clientRects, container))
+ gotSubdocPrivileged = true;
+ if (SpecialPowers.wrap(event).clientRects.length > 0) {
+ if (++fired == 1)
+ setTimeout(check, 100);
+ }
+ }
+
+ function check() {
+ window.opener.is(fired, 1, "Wrong event count (" + frameID + ")");
+ window.opener.ok(gotSubdocPrivileged, "Didn't get subdoc invalidation while we were privileged (" + frameID + ")");
+ window.removeEventListener("MozAfterPaint", listener);
+ runNext();
+ }
+
+ function triggerPaint() {
+ window.addEventListener("MozAfterPaint", listener);
+ document.body.offsetTop;
+ flash(iframe.contentDocument, 'd');
+ }
+ triggerPaint();
+}
+
+var test = 0;
+var tests = [runTest1,
+ function() { runTest2("iframe", "iframe") },
+ function() { runTest2("iframe2", "svg") }];
+function runNext() {
+ if (SpecialPowers.DOMWindowUtils.isMozAfterPaintPending) {
+ // Wait until there are no pending paints before trying to run tests
+ setTimeout(runNext, 100);
+ return;
+ }
+ if (test < tests.length) {
+ ++test;
+ tests[test - 1]();
+ } else {
+ window.opener.finishTests();
+ }
+}
+
+
+]]></script>
+</pre>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug482484-ref.html b/layout/base/tests/bug482484-ref.html
new file mode 100644
index 0000000000..c8b8c2bab3
--- /dev/null
+++ b/layout/base/tests/bug482484-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head></head>
+<body>
+<div contentEditable="true" id="div" spellcheck="false"><p id="p">ABC</p></div>
+<script>
+ // Position the caret after the "A"
+ var div = document.getElementById('div');
+ var p = document.getElementById('p');
+ div.focus();
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 1)
+ range.setEnd(p.firstChild, 1);
+ sel.addRange(range);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug482484.html b/layout/base/tests/bug482484.html
new file mode 100644
index 0000000000..1f05124a2b
--- /dev/null
+++ b/layout/base/tests/bug482484.html
@@ -0,0 +1,22 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contentEditable="true" id="div" spellcheck="false"><p id="p">BC</p></div>
+<script>
+ // Position the caret before the "B"
+ var div = document.getElementById('div');
+ div.focus();
+ var p = document.getElementById('p');
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 0)
+ range.setEnd(p.firstChild, 0);
+ sel.addRange(range);
+
+ sendKey('UP'); // move UP
+ sendChar('A'); // insert "A"
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug503399-ref.html b/layout/base/tests/bug503399-ref.html
new file mode 100644
index 0000000000..2f2af9c31d
--- /dev/null
+++ b/layout/base/tests/bug503399-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 503399</title>
+ <style type="text/css">
+ html, body {
+ color: black;
+ background-color: white;
+ font: 16px monospace;
+ }
+ p {
+ text-align: justify;
+ max-width: 180px;
+ height: 1em;
+ overflow: hidden;
+ position: relative;
+ }
+ span {
+ display: inline-block;
+ border-left: 1px solid black;
+ position: absolute;
+ height: 100%;
+ }
+ </style>
+ <script>
+ var done = false;
+ function runTest(p) {
+ if (done)
+ return;
+ try {
+ getSelection().collapse(p.childNodes[0], 14);
+ } catch (e) {}
+ document.documentElement.removeAttribute('class');
+ done = true;
+ }
+ </script>
+</head>
+<body onload="var p = document.getElementsByTagName('p')[0]; p.focus(); setTimeout(function(){runTest(p)},1000)">
+ <p onfocus="runTest(this)" contentEditable="true">&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;<span></span> &nbsp;&nbsp; &nbsp;&nbsp;</p>
+</body>
+</html>
diff --git a/layout/base/tests/bug503399.html b/layout/base/tests/bug503399.html
new file mode 100644
index 0000000000..96288b1702
--- /dev/null
+++ b/layout/base/tests/bug503399.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
+ <title>Testcase for bug 503399</title>
+ <style type="text/css">
+ html, body {
+ color: black;
+ background-color: white;
+ font: 16px monospace;
+ }
+ p {
+ text-align: justify;
+ max-width: 180px;
+ height: 1em;
+ overflow: hidden;
+ position: relative;
+ }
+ span {
+ display: inline-block;
+ position: absolute;
+ height: 100%;
+ }
+ </style>
+ <script>
+ var done = false;
+ function runTest(p) {
+ if (done)
+ return;
+ try {
+ getSelection().collapse(p.childNodes[0], 14);
+ } catch (e) {}
+ document.documentElement.removeAttribute('class');
+ done = true;
+ }
+ </script>
+</head>
+<body onload="var p = document.getElementsByTagName('p')[0]; p.focus(); setTimeout(function(){runTest(p)},1000)">
+ <p onfocus="runTest(this)" contentEditable="true">&nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;<span></span> &nbsp;&nbsp; &nbsp;&nbsp;</p>
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-1-ref.html b/layout/base/tests/bug512295-1-ref.html
new file mode 100644
index 0000000000..f2faa996c5
--- /dev/null
+++ b/layout/base/tests/bug512295-1-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+x
+<script>
+ // Position the caret at the end of the P element
+ var p = document.getElementById('p');
+ var div = p.parentNode;
+ div.focus();
+ var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ maybeOnSpellCheck(div, function () {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p, p.childNodes.length);
+ range.setEnd(p, p.childNodes.length);
+ sel.addRange(range);
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-1.html b/layout/base/tests/bug512295-1.html
new file mode 100644
index 0000000000..6d2d4bce6b
--- /dev/null
+++ b/layout/base/tests/bug512295-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+x
+<script>
+ // Position the caret after "A"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var p = document.getElementById('p');
+ var t = p.firstChild;
+ range.setStart(t, 1);
+ range.setEnd(t, 1);
+ sel.addRange(range);
+ p.parentNode.focus();
+
+ var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ maybeOnSpellCheck(p.parentNode, function () {
+ sendKey('DOWN'); // now after "1"
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ sendKey('DOWN'); // now make sure we get to the end
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-2-ref.html b/layout/base/tests/bug512295-2-ref.html
new file mode 100644
index 0000000000..acaaf8d982
--- /dev/null
+++ b/layout/base/tests/bug512295-2-ref.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+<script>
+ // Position the caret before the "A"
+ var p = document.getElementById('p');
+ var div = p.parentNode;
+ div.focus();
+ var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ maybeOnSpellCheck(div, function () {
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ range.setStart(p.firstChild, 0);
+ range.setEnd(p.firstChild, 0);
+ sel.addRange(range);
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug512295-2.html b/layout/base/tests/bug512295-2.html
new file mode 100644
index 0000000000..5abd00eb6d
--- /dev/null
+++ b/layout/base/tests/bug512295-2.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML><html class="reftest-wait"><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+x
+<div contenteditable="true">
+<p id="p">A B CD EFG<br>
+ 1234567890</p>
+</div>
+<script>
+ // Position the caret after "A"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var p = document.getElementById('p');
+ var t = p.firstChild;
+ range.setStart(t, 1);
+ range.setEnd(t, 1);
+ sel.addRange(range);
+ p.parentNode.focus();
+
+ var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+ );
+ maybeOnSpellCheck(p.parentNode, function () {
+ sendKey('DOWN'); // now after "1"
+ sendKey('DOWN'); // now below the P element
+ sendKey('UP'); // now before the "1"
+ sendKey('UP'); // now before the "A"
+ sendKey('UP'); // now before the "A"
+ sendKey('UP'); // now before the "A"
+ document.documentElement.classList.remove("reftest-wait");
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug558663.html b/layout/base/tests/bug558663.html
new file mode 100644
index 0000000000..4c5f81153a
--- /dev/null
+++ b/layout/base/tests/bug558663.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=558663
+-->
+<head>
+ <title>Test for Bug 558663</title>
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=558663">Mozilla Bug 558663</a></p>
+
+ <!-- 20x20 of red -->
+<iframe id="iframe" srcdoc="<img id='image' border='0' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAAG0lEQVR42mP8z0A%2BYKJA76jmUc2jmkc1U0EzACKcASfOgGoMAAAAAElFTkSuQmCC'>"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 558663 **/
+var ok = parent.ok;
+var SimpleTest = parent.SimpleTest;
+var compareSnapshots = parent.compareSnapshots;
+var snapshotWindow = parent.snapshotWindow;
+var synthesizeMouse = parent.synthesizeMouse;
+
+window.addEventListener("load", runTest);
+
+function checkSnapshots(s1, s2, shouldBeEqual, testName) {
+ var res = compareSnapshots(s1, s2, shouldBeEqual);
+ if (res[0]) {
+ ok(true, testName + " snapshots compare correctly");
+ } else {
+ ok(false, testName + " snapshots compare incorrectly. snapshot 1: " +
+ res[1] + " snapshot 2: " + res[2]);
+ }
+}
+
+function runTest() {
+ var contentDocument = document.getElementById("iframe").contentDocument;
+ contentDocument.designMode = "on";
+ contentDocument.execCommand("enableObjectResizing", false, true);
+
+ // The editor requires the event loop to spin after you turn on design mode
+ // before it takes effect.
+ setTimeout(continueTest, 100);
+}
+
+function continueTest() {
+ var win = document.getElementById("iframe").contentWindow;
+ var doc = win.document;
+ var image = doc.getElementById("image");
+
+ // We want to test that clicking on the image and then clicking on one of the
+ // draggers doesn't make the draggers disappear.
+
+ // clean snapshot
+ var before = snapshotWindow(win);
+
+ // click to get the draggers
+ synthesizeMouse(image, 1, 1, {type: "mousedown"}, win);
+ synthesizeMouse(image, 1, 1, {type: "mouseup"}, win);
+
+ // mouse over a dragger will change its color, so move the mouse away
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousemove"}, win);
+
+ // snapshot with hopefully draggers
+ var middle = snapshotWindow(win);
+
+ // clicking on the top left dragger shouldn't change anything
+ synthesizeMouse(image, 1, 1, {type: "mousedown"}, win);
+ synthesizeMouse(image, 1, 1, {type: "mouseup"}, win);
+
+ // mouse over a dragger will change its color, so move the mouse away
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousemove"}, win);
+
+ // snapshot with hopefully draggers again
+ var middle2 = snapshotWindow(win);
+
+ // click outside the image (but inside the document) to unselect it
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mousedown"}, win);
+ synthesizeMouse(doc.documentElement, 50, 50, {type: "mouseup"}, win);
+
+ // and then click outside the document so we don't draw a caret
+ synthesizeMouse(document.documentElement, 1, 1, {type: "mousedown"}, window);
+ synthesizeMouse(document.documentElement, 1, 1, {type: "mouseup"}, window);
+
+ // hopefully clean snapshot
+ var end = snapshotWindow(win);
+
+ // before == end && middle == middle2 && before/end != middle/middle2
+ checkSnapshots(before, end, true, "before and after should be the same")
+ checkSnapshots(middle, middle2, true, "middle two should be the same");
+ checkSnapshots(before, middle, false, "before and middle should not be the same");
+ checkSnapshots(before, middle2, false, "before and middle2 should not be the same");
+ checkSnapshots(middle, end, false, "middle and end should not be the same");
+ checkSnapshots(middle2, end, false, "middle2 and end should not be the same");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug583889_inner1.html b/layout/base/tests/bug583889_inner1.html
new file mode 100644
index 0000000000..7a223569d2
--- /dev/null
+++ b/layout/base/tests/bug583889_inner1.html
@@ -0,0 +1,64 @@
+<html>
+<body>
+<iframe id="inner" style="height: 10px; width: 10px"></iframe>
+<div style="width: 1000px; height: 1000px"></div>
+<script type="application/javascript">
+function grabEventAndGo(event) {
+ gen.next(event);
+}
+
+function waitAsync() {
+ setTimeout(function() { gen.next() }, 0);
+}
+
+function postPos() {
+ parent.postMessage(JSON.stringify({ top: document.body.scrollTop,
+ left: document.body.scrollLeft }),
+ "*");
+}
+
+function* runTest() {
+ var inner = document.getElementById("inner");
+ window.onload = grabEventAndGo;
+ // Wait for onLoad event.
+ yield;
+
+ document.body.scrollTop = 300;
+ document.body.scrollLeft = 300;
+
+ postPos();
+
+ inner.src = "bug583889_inner2.html#id1";
+ inner.onload = grabEventAndGo;
+ // Let parent process sent message.
+ // Wait for onLoad event from 'inner' iframe.
+ yield;
+
+ postPos();
+
+ inner.onload = null;
+ dump("hi\n");
+ inner.contentWindow.location = "bug583889_inner2.html#id2"
+ waitAsync();
+ // Let parent process sent message.
+ // Let 'inner' iframe update itself.
+ yield;
+
+ postPos();
+
+ inner.contentWindow.location.hash = "#id3"
+ waitAsync();
+ // Let parent process sent message.
+ // Let 'inner' iframe update itself.
+ yield;
+
+ postPos();
+
+ parent.postMessage("done", "*");
+}
+
+var gen = runTest();
+gen.next();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug583889_inner2.html b/layout/base/tests/bug583889_inner2.html
new file mode 100644
index 0000000000..ce63f54cfd
--- /dev/null
+++ b/layout/base/tests/bug583889_inner2.html
@@ -0,0 +1,5 @@
+<body>
+<a id="id1">link 1</a>
+<a id="id2">link 2</a>
+<a id="id3">link 3</a>
+</body>
diff --git a/layout/base/tests/bug585922-ref.html b/layout/base/tests/bug585922-ref.html
new file mode 100644
index 0000000000..8009314c86
--- /dev/null
+++ b/layout/base/tests/bug585922-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="doTest()">
+ <input type=text style="-moz-appearance: none">
+ <script>
+ function doTest() {
+ var d = document.querySelector("input");
+ d.value = "b";
+ d.focus();
+ var editor = SpecialPowers.wrap(d).editor;
+ var sel = editor.selection;
+ var t = editor.rootElement.firstChild;
+ sel.collapse(t, 1); // put the caret at the end of the textbox
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug585922.html b/layout/base/tests/bug585922.html
new file mode 100644
index 0000000000..4f4eaeb6b9
--- /dev/null
+++ b/layout/base/tests/bug585922.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="doTest()">
+ <input type=text style="-moz-appearance: none">
+ <script>
+ function doTest() {
+ function enableCaret(aEnable) {
+ var selCon = editor.selectionController;
+ selCon.setCaretEnabled(aEnable);
+ }
+
+ var d = document.querySelector("input");
+ d.value = "a";
+ d.focus();
+ var editor = SpecialPowers.wrap(d).editor;
+ var sel = editor.selection;
+ var t = editor.rootElement.firstChild;
+ sel.collapse(t, 1); // put the caret at the end of the div
+ setTimeout(function() {
+ enableCaret(false);enableCaret(true);// force a caret display
+ enableCaret(false); // hide the caret
+ t.replaceData(0, 1, "b"); // replace the text node data
+ // at this point, the selection is collapsed to offset 0
+ synthesizeQuerySelectedText(); // call nsCaret::GetGeometry
+ sel.collapse(t, 1); // put the caret at the end again
+ enableCaret(true); // show the caret again
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug597519-1-ref.html b/layout/base/tests/bug597519-1-ref.html
new file mode 100644
index 0000000000..e11eb0c967
--- /dev/null
+++ b/layout/base/tests/bug597519-1-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none">ab
+</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug597519-1.html b/layout/base/tests/bug597519-1.html
new file mode 100644
index 0000000000..2fcddce515
--- /dev/null
+++ b/layout/base/tests/bug597519-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea maxlength="3" spellcheck="false" style="-moz-appearance: none"></textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ sendString("ab");
+ synthesizeKey("KEY_Enter");
+ sendString("c");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-1-ref.html b/layout/base/tests/bug602141-1-ref.html
new file mode 100644
index 0000000000..64cbd58c3d
--- /dev/null
+++ b/layout/base/tests/bug602141-1-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span contenteditable="true" spellcheck="false">navigable__</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-1.html b/layout/base/tests/bug602141-1.html
new file mode 100644
index 0000000000..4a3d3614ee
--- /dev/null
+++ b/layout/base/tests/bug602141-1.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span contenteditable="true" spellcheck="false">navigable__</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-2-ref.html b/layout/base/tests/bug602141-2-ref.html
new file mode 100644
index 0000000000..f54518a024
--- /dev/null
+++ b/layout/base/tests/bug602141-2-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span id="x" contenteditable="true" spellcheck="false">navigable__|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 13);
+ range.setEnd(t, 13);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-2.html b/layout/base/tests/bug602141-2.html
new file mode 100644
index 0000000000..e86c906f3c
--- /dev/null
+++ b/layout/base/tests/bug602141-2.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span id="x" contenteditable="true" spellcheck="false">navigable__|</span><br />
+<script>
+ document.getElementById('x').appendChild(document.createTextNode('unnavigable'));
+
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 12);
+ range.setEnd(t, 12);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-3-ref.html b/layout/base/tests/bug602141-3-ref.html
new file mode 100644
index 0000000000..8d39318ccd
--- /dev/null
+++ b/layout/base/tests/bug602141-3-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+noteditable<span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-3.html b/layout/base/tests/bug602141-3.html
new file mode 100644
index 0000000000..fa153a6079
--- /dev/null
+++ b/layout/base/tests/bug602141-3.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+noteditable<span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span><br />
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-4-ref.html b/layout/base/tests/bug602141-4-ref.html
new file mode 100644
index 0000000000..c67986c5f0
--- /dev/null
+++ b/layout/base/tests/bug602141-4-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE HTML><html><head>
+</head>
+<body>
+<span>not editable</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span>
+<script>
+ // Position the caret after "u"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 11);
+ range.setEnd(t, 11);
+ sel.addRange(range);
+ x.focus();
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug602141-4.html b/layout/base/tests/bug602141-4.html
new file mode 100644
index 0000000000..b2c8185ab8
--- /dev/null
+++ b/layout/base/tests/bug602141-4.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<span>not editable</span><span id="x" contenteditable="true" spellcheck="false">navigable|unnavigable</span>
+<script>
+ // Position the caret after "|"
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ var range = document.createRange();
+ var x = document.getElementById('x');
+ var t = x.firstChild;
+ range.setStart(t, 10);
+ range.setEnd(t, 10);
+ sel.addRange(range);
+ x.focus();
+
+ sendKey('RIGHT'); // Try to move the caret one position to the right
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-1.html b/layout/base/tests/bug612271-1.html
new file mode 100644
index 0000000000..226d7e7d07
--- /dev/null
+++ b/layout/base/tests/bug612271-1.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeydown="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-2.html b/layout/base/tests/bug612271-2.html
new file mode 100644
index 0000000000..8767468131
--- /dev/null
+++ b/layout/base/tests/bug612271-2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeypress="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-3.html b/layout/base/tests/bug612271-3.html
new file mode 100644
index 0000000000..9f267c2824
--- /dev/null
+++ b/layout/base/tests/bug612271-3.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+ <textarea id="target" style="height: 100px; -moz-appearance: none" spellcheck="false"
+ onkeyup="this.style.display='block';this.style.height='200px';">foo</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+ sendKey('RETURN');
+ document.body.appendChild(document.createTextNode(t.selectionStart + " - " + t.selectionEnd));
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug612271-ref.html b/layout/base/tests/bug612271-ref.html
new file mode 100644
index 0000000000..0d3e96b98a
--- /dev/null
+++ b/layout/base/tests/bug612271-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script type="text/javascript">
+ function loaded() {
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = 4;
+ }
+ </script>
+</head>
+<body onload="loaded()">
+ <textarea style="height: 200px; display: block; -moz-appearance: none" spellcheck="false"
+ >foo
+</textarea>
+ 4 - 4
+</body>
+</html>
diff --git a/layout/base/tests/bug613433-1.html b/layout/base/tests/bug613433-1.html
new file mode 100644
index 0000000000..837dff89da
--- /dev/null
+++ b/layout/base/tests/bug613433-1.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow-x: auto;
+ }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-2.html b/layout/base/tests/bug613433-2.html
new file mode 100644
index 0000000000..84d55e7be5
--- /dev/null
+++ b/layout/base/tests/bug613433-2.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow-y: auto;
+ }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-3.html b/layout/base/tests/bug613433-3.html
new file mode 100644
index 0000000000..aa7b2853a4
--- /dev/null
+++ b/layout/base/tests/bug613433-3.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ overflow: auto;
+ }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ // type a character, then press backspace to delete it
+ sendChar("X");
+ sendKey("BACK_SPACE");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div id="div1" contenteditable></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613433-ref.html b/layout/base/tests/bug613433-ref.html
new file mode 100644
index 0000000000..f4a2ab3b6f
--- /dev/null
+++ b/layout/base/tests/bug613433-ref.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <style>
+ div {
+ min-height: 36px;
+ }
+ </style>
+ <script>
+ function test() {
+ document.querySelector("div").focus();
+ }
+ function focusTriggered() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </head>
+ <body onload="test()">
+ <div contenteditable onfocus="focusTriggered()"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/bug613807-1-ref.html b/layout/base/tests/bug613807-1-ref.html
new file mode 100644
index 0000000000..b47a572ea7
--- /dev/null
+++ b/layout/base/tests/bug613807-1-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body onload="document.querySelector('textarea').focus()">
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+</body>
+</html>
diff --git a/layout/base/tests/bug613807-1.html b/layout/base/tests/bug613807-1.html
new file mode 100644
index 0000000000..79592fed7a
--- /dev/null
+++ b/layout/base/tests/bug613807-1.html
@@ -0,0 +1,90 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea id="t" rows="4" style="-moz-appearance: none"></textarea>
+<script>
+ if (typeof(addLoadEvent) == 'undefined') {
+ _newCallStack = function(path) {
+ var rval = function () {
+ var callStack = arguments.callee.callStack;
+ for (var i = 0; i < callStack.length; i++) {
+ if (callStack[i].apply(this, arguments) === false) {
+ break;
+ }
+ }
+ try {
+ this[path] = null;
+ } catch (e) {
+ // pass
+ }
+ };
+ rval.callStack = [];
+ return rval;
+ };
+ function addLoadEvent(func) {
+ var existing = window.onload;
+ var regfunc = existing;
+ if (!(typeof(existing) == 'function'
+ && typeof(existing.callStack) == "object"
+ && existing.callStack !== null)) {
+ regfunc = _newCallStack("onload");
+ if (typeof(existing) == 'function') {
+ regfunc.callStack.push(existing);
+ }
+ window.onload = regfunc;
+ }
+ regfunc.callStack.push(func);
+ };
+ }
+
+ addLoadEvent(function() {
+ var area = document.getElementById('t');
+ area.focus();
+
+ var domWindowUtils = SpecialPowers.getDOMWindowUtils(window);
+
+ // input raw characters
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u306D",
+ clauses: [
+ { length: 1, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ });
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u306D\u3053",
+ clauses: [
+ { length: 2, attr: COMPOSITION_ATTR_RAW_CLAUSE }
+ ]
+ },
+ caret: { start: 2, length: 0 }
+ });
+
+ // convert
+ synthesizeCompositionChange(
+ { composition:
+ { string: "\u732B",
+ clauses: [
+ { length: 1, attr: COMPOSITION_ATTR_SELECTED_CLAUSE }
+ ]
+ },
+ caret: { start: 1, length: 0 }
+ });
+
+ // commit
+ synthesizeComposition({ type: "compositioncommitasis" });
+
+ document.body.clientWidth;
+
+ // undo
+ synthesizeKey("Z", {accelKey: true});
+ });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug632215-1.html b/layout/base/tests/bug632215-1.html
new file mode 100644
index 0000000000..c64330a6de
--- /dev/null
+++ b/layout/base/tests/bug632215-1.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <iframe srcdoc="<body spellcheck=false></body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ var s = w.getSelection();
+ i.focus();
+ d.body.contentEditable = true;
+ d.body.contentEditable = false;
+ d.designMode = "off";
+ d.designMode = "on";
+ d.body.focus();
+ sendString("x");
+ s.collapse(d.body.firstChild, 1);
+ sendString("x");
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug632215-2.html b/layout/base/tests/bug632215-2.html
new file mode 100644
index 0000000000..02b0acd953
--- /dev/null
+++ b/layout/base/tests/bug632215-2.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body>
+ <iframe srcdoc="<body contenteditable spellcheck=false></body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ var s = w.getSelection();
+ i.focus();
+ d.body.contentEditable = false;
+ d.designMode = "off";
+ d.designMode = "on";
+ d.body.focus();
+ sendString("x");
+ s.collapse(d.body.firstChild, 1);
+ sendString("x");
+ setTimeout(function() {
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug632215-ref.html b/layout/base/tests/bug632215-ref.html
new file mode 100644
index 0000000000..b0e4bcff7f
--- /dev/null
+++ b/layout/base/tests/bug632215-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <iframe srcdoc="<body spellcheck=false>xx</body>"></iframe>
+ <script>
+ onload = function() {
+ var i = document.querySelector("iframe");
+ var d = i.contentDocument;
+ var w = i.contentWindow;
+ d.designMode = "on";
+ i.focus();
+ d.body.focus();
+ w.getSelection().collapse(d.body.firstChild, 2);
+ };
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug633044-1-ref.html b/layout/base/tests/bug633044-1-ref.html
new file mode 100644
index 0000000000..330d5777f9
--- /dev/null
+++ b/layout/base/tests/bug633044-1-ref.html
@@ -0,0 +1,16 @@
+<html>
+ <head>
+ <script>
+ onload = function() {
+ var el;
+ while (el = document.querySelector("br")) {
+ el.remove();
+ }
+ focus();
+ document.body.focus();
+ getSelection().collapse(document.body.firstChild, 0);
+ }
+ </script>
+</head>
+
+<body style="white-space:pre-wrap;" contenteditable></body></html>
diff --git a/layout/base/tests/bug633044-1.html b/layout/base/tests/bug633044-1.html
new file mode 100644
index 0000000000..7add9020de
--- /dev/null
+++ b/layout/base/tests/bug633044-1.html
@@ -0,0 +1,24 @@
+<html>
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ onload = function() {
+ var el;
+ while (el = document.querySelector("br")) {
+ el.remove();
+ }
+ focus();
+ document.body.focus();
+ getSelection().collapse(document.body.firstChild, 0);
+
+ var range = window.getSelection().getRangeAt(0);
+ var el = document.createTextNode(" ");
+ range.insertNode(el);
+ el.remove();
+
+ synthesizeKey("KEY_ArrowUp");
+ }
+ </script>
+</head>
+
+<body style="white-space:pre-wrap;" contenteditable></body></html>
diff --git a/layout/base/tests/bug634406-1-ref.html b/layout/base/tests/bug634406-1-ref.html
new file mode 100644
index 0000000000..87b42a9ede
--- /dev/null
+++ b/layout/base/tests/bug634406-1-ref.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML><html>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none">ab</textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+ t.selectionStart = t.selectionEnd = t.value.length;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug634406-1.html b/layout/base/tests/bug634406-1.html
new file mode 100644
index 0000000000..1b13a4c636
--- /dev/null
+++ b/layout/base/tests/bug634406-1.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML><html><head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<textarea spellcheck="false" style="-moz-appearance: none"></textarea>
+<script>
+ var t = document.querySelector("textarea");
+ t.focus();
+
+ sendString("a");
+ synthesizeKey("A", {accelKey: true});
+ synthesizeKey("KEY_ArrowRight");
+ sendString("b");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug644428-1-ref.html b/layout/base/tests/bug644428-1-ref.html
new file mode 100644
index 0000000000..9a771db851
--- /dev/null
+++ b/layout/base/tests/bug644428-1-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="setupCaret()">
+ <div contenteditable>a</div>
+ <script>
+ function setupCaret() {
+ var div = document.querySelector("div");
+ div.focus();
+ var sel = window.getSelection();
+ sel.collapse(div, 1);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug644428-1.html b/layout/base/tests/bug644428-1.html
new file mode 100644
index 0000000000..d304beb97f
--- /dev/null
+++ b/layout/base/tests/bug644428-1.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="setupCaret()" spellcheck="false">
+ <div contenteditable>a<span>b</span>c </div>
+ <script>
+ function setupCaret() {
+ var div = document.querySelector("div");
+ div.focus();
+ var sel = window.getSelection();
+ sel.collapse(div, 3);
+ synthesizeKey("KEY_Backspace");
+ synthesizeKey("KEY_Backspace");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-1-ref.html b/layout/base/tests/bug646382-1-ref.html
new file mode 100644
index 0000000000..e9d37339e4
--- /dev/null
+++ b/layout/base/tests/bug646382-1-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <textarea onfocus="done()"
+ style="-moz-appearance: none;
+ unicode-bidi: bidi-override;">س</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ textarea.selectionStart = 1; // place caret after the letter
+ textarea.selectionEnd = 1;
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-1.html b/layout/base/tests/bug646382-1.html
new file mode 100644
index 0000000000..949d5c650a
--- /dev/null
+++ b/layout/base/tests/bug646382-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea onfocus="typeIntoMe()" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ sendString("س");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-2-ref.html b/layout/base/tests/bug646382-2-ref.html
new file mode 100644
index 0000000000..22aa1b7d1d
--- /dev/null
+++ b/layout/base/tests/bug646382-2-ref.html
@@ -0,0 +1,18 @@
+<html class="reftest-wait">
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()"
+ style="-moz-appearance: none;
+ unicode-bidi: bidi-override;">s</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ textarea.selectionStart = 1; // place caret after the letter
+ textarea.selectionEnd = 1;
+ }
+ function done() {
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug646382-2.html b/layout/base/tests/bug646382-2.html
new file mode 100644
index 0000000000..5ee365990c
--- /dev/null
+++ b/layout/base/tests/bug646382-2.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="typeIntoMe()" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ sendString("s");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-1-ref.html b/layout/base/tests/bug664087-1-ref.html
new file mode 100644
index 0000000000..e0ebc128cd
--- /dev/null
+++ b/layout/base/tests/bug664087-1-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea rows="3" onfocus="done()" spellcheck="false" style="-moz-appearance: none">×ב
+×’</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_ArrowLeft");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-1.html b/layout/base/tests/bug664087-1.html
new file mode 100644
index 0000000000..0541788816
--- /dev/null
+++ b/layout/base/tests/bug664087-1.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea rows="3" onfocus="typeIntoMe()" spellcheck="false" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ sendString("×");
+ synthesizeKey("KEY_Enter");
+ sendString("×’");
+ synthesizeKey("KEY_ArrowUp");
+ synthesizeKey("KEY_End");
+ sendString("ב");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-2-ref.html b/layout/base/tests/bug664087-2-ref.html
new file mode 100644
index 0000000000..a30c3e7fe5
--- /dev/null
+++ b/layout/base/tests/bug664087-2-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="done()" spellcheck="false" style="-moz-appearance: none">ab
+c</textarea>
+ <script>
+ var textarea = document.querySelector("textarea");
+ function start() {
+ textarea.focus();
+ }
+ function done() {
+ synthesizeKey("KEY_ArrowRight");
+ synthesizeKey("KEY_ArrowRight");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug664087-2.html b/layout/base/tests/bug664087-2.html
new file mode 100644
index 0000000000..250f59c887
--- /dev/null
+++ b/layout/base/tests/bug664087-2.html
@@ -0,0 +1,25 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <textarea dir="rtl" onfocus="typeIntoMe()" spellcheck="false" style="-moz-appearance: none"></textarea>
+ <script>
+ function start() {
+ document.querySelector("textarea").focus();
+ }
+ function typeIntoMe() {
+ setTimeout(function() {
+ sendString("a");
+ synthesizeKey("KEY_Enter");
+ sendString("c");
+ synthesizeKey("KEY_ArrowUp");
+ synthesizeKey("KEY_End");
+ sendString("b");
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug682712-1-ref.html b/layout/base/tests/bug682712-1-ref.html
new file mode 100644
index 0000000000..642d8f97a1
--- /dev/null
+++ b/layout/base/tests/bug682712-1-ref.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ </head>
+ <body onload="start()">
+ <iframe srcdoc="<body contenteditable spellcheck=false>foo bar"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+
+ setTimeout(function() {
+ doc.body.focus();
+
+ // Now try to set the caret without moving it
+ win.getSelection().collapse(doc.body.firstChild, 1);
+
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug682712-1.html b/layout/base/tests/bug682712-1.html
new file mode 100644
index 0000000000..1cca33bf44
--- /dev/null
+++ b/layout/base/tests/bug682712-1.html
@@ -0,0 +1,32 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe srcdoc="<body contenteditable spellcheck=false>foo bar"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+
+ // Reframe the iframe
+ iframe.style.display = "none";
+ document.body.clientWidth;
+ iframe.style.display = "";
+ document.body.clientWidth;
+
+ setTimeout(function() {
+ doc.body.focus();
+
+ // Now try to move the caret
+ win.getSelection().collapse(doc.body.firstChild, 0);
+ synthesizeKey("KEY_ArrowRight");
+
+ document.documentElement.removeAttribute("class");
+ }, 0);
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug687297_a.html b/layout/base/tests/bug687297_a.html
new file mode 100644
index 0000000000..af0010834c
--- /dev/null
+++ b/layout/base/tests/bug687297_a.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_a(document.getElementById("test_content").clientHeight);
+ window.location.href = "bug687297_b.html";
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug687297_b.html b/layout/base/tests/bug687297_b.html
new file mode 100644
index 0000000000..34f682354d
--- /dev/null
+++ b/layout/base/tests/bug687297_b.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_b(document.getElementById("test_content").clientHeight);
+ window.location.href = "bug687297_c.html";
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug687297_c.html b/layout/base/tests/bug687297_c.html
new file mode 100644
index 0000000000..ea029d24e5
--- /dev/null
+++ b/layout/base/tests/bug687297_c.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
+<html>
+<head>
+ <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
+ <title>Test companion for Bug 687297</title>
+ <style type="text/css"> * { font-size:9px; } </style>
+</head>
+<body>
+ <div id="test_content">ABCDEFG 0123456</div>
+</body>
+<script type="application/javascript">
+ window.onload = function() {
+ opener.report_size_c(document.getElementById("test_content").clientHeight);
+ window.close();
+ };
+</script>
+</html>
diff --git a/layout/base/tests/bug746993-1-ref.html b/layout/base/tests/bug746993-1-ref.html
new file mode 100644
index 0000000000..309a7261e8
--- /dev/null
+++ b/layout/base/tests/bug746993-1-ref.html
@@ -0,0 +1,20 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe srcdoc="<body contenteditable spellcheck=false>Here's some text.<br /><br /><div></div></body>"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+ iframe.focus();
+ doc.body.focus();
+ win.getSelection().collapse(doc.body, 3);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug746993-1.html b/layout/base/tests/bug746993-1.html
new file mode 100644
index 0000000000..8b17db7dbe
--- /dev/null
+++ b/layout/base/tests/bug746993-1.html
@@ -0,0 +1,22 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <iframe srcdoc="<body contenteditable spellcheck=false><br /><div></div></body>"></iframe>
+ <script>
+ function start() {
+ var iframe = document.querySelector("iframe");
+ var win = iframe.contentWindow;
+ var doc = iframe.contentDocument;
+ iframe.focus();
+ doc.body.focus();
+ win.getSelection().collapse(doc.body, 0);
+ sendString("Here's some text.");
+ synthesizeKey("KEY_Enter");
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug851445_helper.html b/layout/base/tests/bug851445_helper.html
new file mode 100644
index 0000000000..dc4e4002e6
--- /dev/null
+++ b/layout/base/tests/bug851445_helper.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<body style="height:1000px">
+<script>
+var docElement = document.documentElement;
+docElement.style.display = 'none';
+docElement.offsetTop;
+docElement.style.display = '';
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug923376-ref.html b/layout/base/tests/bug923376-ref.html
new file mode 100644
index 0000000000..859e5023ef
--- /dev/null
+++ b/layout/base/tests/bug923376-ref.html
@@ -0,0 +1,12 @@
+<!doctype html>
+<html class="reftest-wait"><div contenteditable>something missspelled<br>something elsed#</div>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+document.body.firstChild.focus();
+var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+);
+maybeOnSpellCheck(document.body.firstChild, function() {
+ document.documentElement.removeAttribute("class");
+});
+</script>
diff --git a/layout/base/tests/bug923376.html b/layout/base/tests/bug923376.html
new file mode 100644
index 0000000000..89e920394f
--- /dev/null
+++ b/layout/base/tests/bug923376.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html class="reftest-wait"><div contenteditable></div>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+var div = document.body.firstChild;
+div.focus();
+var { maybeOnSpellCheck } = SpecialPowers.ChromeUtils.importESModule(
+ "resource://testing-common/AsyncSpellCheckTestHelper.sys.mjs"
+);
+maybeOnSpellCheck(div, function() {
+ div.innerHTML = 'something missspelled<br>something elsed#';
+ maybeOnSpellCheck(div, function() {
+ document.documentElement.removeAttribute("class");
+ });
+});
+</script>
diff --git a/layout/base/tests/bug956530-1-ref.html b/layout/base/tests/bug956530-1-ref.html
new file mode 100644
index 0000000000..d998b6d8b5
--- /dev/null
+++ b/layout/base/tests/bug956530-1-ref.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <style>
+ /* eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ async function test() {
+ let input = document.querySelector("input");
+ let waitForFocus = new Promise(resolve => {
+ input.addEventListener("focus", resolve, {once: true});
+ });
+ window.focus();
+ input.focus();
+ await waitForFocus;
+ input.select();
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body onload="SimpleTest.executeSoon(test);">
+ <input value="text text text text text">
+ </body>
+</html>
diff --git a/layout/base/tests/bug956530-1.html b/layout/base/tests/bug956530-1.html
new file mode 100644
index 0000000000..68cbc04258
--- /dev/null
+++ b/layout/base/tests/bug956530-1.html
@@ -0,0 +1,35 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+ <style>
+ /* eliminate the blue glow when focusing the element. */
+ input {
+ background: none;
+ border: none;
+ outline: none;
+ }
+ </style>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ async function test() {
+ let input = document.querySelector("input");
+ input.setSelectionRange(input.value.length, input.value.length);
+ window.focus();
+ // Run input.onfocus
+ synthesizeMouseAtCenter(input, {});
+ // Blur
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+ synthesizeMouseAtCenter(document.body, {});
+ // Run input.onfocus again
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+ synthesizeMouseAtCenter(input, {});
+ // Check the result
+ await new Promise(resolve => { SimpleTest.executeSoon(resolve); });
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ <body onload="SimpleTest.executeSoon(test);">
+ <input value="text text text text text"
+ onfocus="this.select();">
+ </body>
+</html>
diff --git a/layout/base/tests/bug966992-1-ref.html b/layout/base/tests/bug966992-1-ref.html
new file mode 100644
index 0000000000..c8fff7b5ed
--- /dev/null
+++ b/layout/base/tests/bug966992-1-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:padding-box;
+}
+.rel { position:relative; }
+.mask1 { position:absolute; width:20px; background:white; top:0; bottom:0; right:0; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="rel block">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<span style="padding-right:20px">X</span><div class=mask1></div></div>
+<mask></mask>
+</div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-1.html b/layout/base/tests/bug966992-1.html
new file mode 100644
index 0000000000..f630ff1c6b
--- /dev/null
+++ b/layout/base/tests/bug966992-1.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:content-box;
+}
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block">XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div>
+<mask></mask>
+</div>
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-2-ref.html b/layout/base/tests/bug966992-2-ref.html
new file mode 100644
index 0000000000..a619d579c6
--- /dev/null
+++ b/layout/base/tests/bug966992-2-ref.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:padding-box;
+ line-height:1px;
+}
+.rel { position:relative; }
+.mask1 { position:absolute; width:20px; background:white; top:0; bottom:0; right:0; }
+.mask2 { position:absolute; height:20px; background:white; top:0; left:40px; right:0; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="rel block">&nbsp;&nbsp;&nbsp;&nbsp;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX<span style="padding-right:20px">X</span><div class=mask2></div><div class=mask1></div></div>
+<mask></mask>
+</div>
+
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/bug966992-2.html b/layout/base/tests/bug966992-2.html
new file mode 100644
index 0000000000..1a8919e55d
--- /dev/null
+++ b/layout/base/tests/bug966992-2.html
@@ -0,0 +1,38 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcases for overflow-clip-box:content-box</title>
+ <style type="text/css">
+ html,body {
+ color:black; background-color:white; font:16px monospace; padding:0; margin:7px;
+ }
+.block {
+ border:1px solid grey; height:50px; width:200px; padding:20px;
+ overflow:auto; overflow-clip-box:content-box;
+ line-height:1px;
+}
+.rel { position:relative; }
+mask {
+ display:block;
+ position:absolute;
+ left: -1px;
+ bottom: -1px;
+ height: 25px;
+ width: 80%;
+ background:black;
+}
+ </style>
+</head>
+<body>
+
+<div style="position:relative;">
+<div contenteditable=true spellcheck=false tabindex=0 id=x class="block">&nbsp;&nbsp;&nbsp;&nbsp;XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX</div>
+<mask></mask>
+</div>
+<script>
+var x = document.getElementById('x');
+x.focus();
+window.getSelection().collapse(x,0);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/bug970964_inner.html b/layout/base/tests/bug970964_inner.html
new file mode 100644
index 0000000000..e8e7092aea
--- /dev/null
+++ b/layout/base/tests/bug970964_inner.html
@@ -0,0 +1,357 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970964
+-->
+<head>
+ <title>Test for Bug 970964</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=970964">Mozilla Bug 970964</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 970964 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+function testtouch(aOptions) {
+ if (!aOptions)
+ aOptions = {};
+ this.identifier = aOptions.identifier || 0;
+ this.target = aOptions.target || 0;
+ this.page = aOptions.page || {x: 0, y: 0};
+ this.radius = aOptions.radius || {x: 0, y: 0};
+ this.rotationAngle = aOptions.rotationAngle || 0;
+ this.force = aOptions.force || 1;
+}
+
+function touchEvent(aOptions) {
+ if (!aOptions) {
+ aOptions = {};
+ }
+ this.ctrlKey = aOptions.ctrlKey || false;
+ this.altKey = aOptions.altKey || false;
+ this.shiftKey = aOptions.shiftKey || false;
+ this.metaKey = aOptions.metaKey || false;
+ this.touches = aOptions.touches || [];
+ this.targetTouches = aOptions.targetTouches || [];
+ this.changedTouches = aOptions.changedTouches || [];
+}
+
+function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
+ var ids = [], xs=[], ys=[], rxs = [], rys = [],
+ rotations = [], forces = [], tiltXs = [], tiltYs = [], twists = [];
+
+ for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
+ for (var i = 0; i < aEvent[touchType].length; i++) {
+ if (!ids.includes(aEvent[touchType][i].identifier)) {
+ ids.push(aEvent[touchType][i].identifier);
+ xs.push(aEvent[touchType][i].page.x);
+ ys.push(aEvent[touchType][i].page.y);
+ rxs.push(aEvent[touchType][i].radius.x);
+ rys.push(aEvent[touchType][i].radius.y);
+ rotations.push(aEvent[touchType][i].rotationAngle);
+ forces.push(aEvent[touchType][i].force);
+ tiltXs.push(0);
+ tiltYs.push(0);
+ twists.push(0);
+ }
+ }
+ }
+ return windowUtils.sendTouchEvent(aType,
+ ids, xs, ys, rxs, rys,
+ rotations, forces, tiltXs, tiltYs, twists,
+ aModifiers, 0);
+}
+
+function getDefaultArgEvent(eventname) {
+ return new PointerEvent(eventname, {
+ bubbles: true, cancelable: true, view: window,
+ detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0,
+ ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
+ button: 0, relatedTarget: null, pointerId: 0
+ });
+}
+
+function getTouchEventForTarget(target, cwu, id) {
+ var bcr = target.getBoundingClientRect();
+ var touch = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: id,
+ });
+ var event = new touchEvent({
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch]
+ });
+ return event;
+}
+
+function runTests() {
+ var d0 = document.getElementById("d0");
+ var d1 = document.getElementById("d1");
+ var d2 = document.getElementById("d2");
+ var d3 = document.getElementById("d3");
+
+ // Test Pointer firing before any mouse/touch original source
+
+ var mouseDownTriggered = 0;
+ var pointerDownTriggered = 0;
+ var touchDownTriggered = 0;
+ var touchCancelTriggered = 0;
+ var pointerCancelTriggered = 0;
+
+ // Test pointer event generated from mouse event
+ d0.addEventListener("mousedown", (e) => {
+ ++mouseDownTriggered;
+ is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointerdown", (e) => {
+ ++pointerDownTriggered;
+ is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ }, {once: true});
+
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ synthesizeMouse(d1, 3, 3, { type: "mousedown"});
+ synthesizeMouse(d1, 3, 3, { type: "mouseup"});
+
+ // Test pointer event generated from touch event
+ mouseDownTriggered = 0;
+ pointerDownTriggered = 0;
+
+ d0.addEventListener("touchstart", (e) => {
+ ++touchDownTriggered;
+ is(pointerDownTriggered, touchDownTriggered, "Touch event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("mousedown", (e) => {
+ ++mouseDownTriggered;
+ is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointerdown", (e) => {
+ ++pointerDownTriggered;
+ is(pointerDownTriggered, touchDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ }, {once: true});
+
+ d0.addEventListener("touchcancel", (e) => {
+ ++touchCancelTriggered;
+ is(pointerCancelTriggered, touchCancelTriggered, "Touch cancel event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointercancel", function(ev) {
+ is(ev.pointerId, 0, "Correct default pointerId");
+ is(ev.bubbles, true, "bubbles should be true");
+ is(ev.cancelable, false, "pointercancel cancelable should be false ");
+ ++pointerCancelTriggered;
+ is(pointerCancelTriggered, touchCancelTriggered + 1, "Pointer event must be triggered before touch event!");
+ }, {once: true});
+
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+ var event1 = getTouchEventForTarget(d1, cwu, 0);
+ sendTouchEvent(cwu, "touchstart", event1, 0);
+ sendTouchEvent(cwu, "touchmove", event1, 0);
+ // Test Touch to Pointer Cancel
+ sendTouchEvent(cwu, "touchcancel", event1, 0);
+
+ // Check Pointer enter/leave from mouse generated event
+ var mouseEnterTriggered = 0;
+ var pointerEnterTriggered = 0;
+ d2.onpointerenter = function(e) {
+ pointerEnterTriggered = 1;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ is(mouseEnterTriggered, 0, "Pointer event must be triggered before mouse event!");
+ };
+ d2.onmouseenter = function(e) {
+ mouseEnterTriggered = 1;
+ is(pointerEnterTriggered , 1, "Mouse event must be triggered after pointer event!");
+ };
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ d2.onmouseenter = function(e) {}
+
+ // Test Multi Pointer enter/leave for pointers generated from Mouse and Touch at the same time
+ // Enter mouse and touch generated pointers to different elements
+ var d1enterCount = 0;
+ var d2enterCount = 0;
+ var d3enterCount = 0;
+ var d1leaveCount = 0;
+ var d2leaveCount = 0;
+ var d3leaveCount = 0;
+ var mousePointerEnterLeaveCount = 0;
+ var touchPointerEnterLeaveCount = 0;
+
+ var checkPointerType = function(pointerType) {
+ if (pointerType == "mouse") {
+ ++mousePointerEnterLeaveCount;
+ } else if (pointerType == "touch") {
+ ++touchPointerEnterLeaveCount;
+ }
+ };
+
+ d1.onpointerenter = function(e) {
+ ++d1enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerenter = function(e) {
+ ++d2enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerenter = function(e) {
+ ++d3enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d1.onpointerleave = function(e) {
+ ++d1leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerleave = function(e) {
+ ++d2leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerleave = function(e) {
+ ++d3leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ sendTouchEvent(cwu, "touchstart", getTouchEventForTarget(d3, cwu, 3), 0);
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d3, cwu, 3), 0);
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 2, "Wrong mouse enterLeave count for!");
+
+ is(d1enterCount, 1, "Wrong enter count for! d1");
+ is(d2leaveCount, 1, "Wrong leave count for! d2");
+ is(d3enterCount, 1, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d3, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 3, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 4, "Wrong mouse enterLeave count for!");
+
+ is(d3leaveCount, 1, "Wrong leave count for! d3");
+ is(d1leaveCount, 1, "Wrong leave count for! d1");
+ is(d1enterCount, 2, "Wrong enter count for! d1");
+ is(d3enterCount, 2, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d2, cwu, 3), 0);
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 5, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 6, "Wrong mouse enterLeave count for!");
+
+ is(d1leaveCount, 2, "Wrong leave count for! d1");
+ is(d2enterCount, 2, "Wrong enter count for! d2");
+ is(d3leaveCount, 2, "Wrong leave count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 7, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 8, "Wrong mouse enterLeave count for!");
+
+ is(d2leaveCount, 3, "Wrong leave count for! d2");
+ is(d1enterCount, 4, "Wrong enter count for! d1");
+
+ // Test for pointer buttons when it generated from mousemove event
+ d1.onpointermove = function(e) {
+ is(e.buttons, 0, "Buttons must be 0 on pointer generated from mousemove");
+ is(e.button, -1, "Button must be -1 on pointer generated from mousemove when no buttons pressed");
+ is(e.pointerType, "mouse", "Pointer type must be mouse");
+ };
+ cwu.sendMouseEvent("mousemove", 4, 4, 0, 0, 0, false, 0, 0);
+
+ d1.onpointermove = function(e) {
+ is(e.buttons, 1, "Buttons must be 1 on pointermove generated from touch event");
+ is(e.button, -1, "Button must be -1 on pointermove generated from touch event");
+ is(e.pointerType, "touch", "Pointer type must be touch");
+ };
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 2), 0);
+
+ // Test for cancel trigger pointerOut (Touch Pointer must be at d1 now)
+ pointerCancelTriggered = 0;
+ var pointerOutTriggeredForCancelEvent = 0;
+ var pointerLeaveTriggeredForCancelEvent = 0;
+ d1.onpointerout = function(e) {
+ if (pointerOutTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerOutTriggeredForCancelEvent = 1;
+ };
+ d1.onpointerleave = function(e) {
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out must be dispatched bedore Pointer leave");
+ if (pointerLeaveTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerLeaveTriggeredForCancelEvent = 1;
+ }
+
+ sendTouchEvent(cwu, "touchcancel", getTouchEventForTarget(d1, cwu, 3), 0);
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out not dispatched on PointerCancel");
+ is(pointerLeaveTriggeredForCancelEvent, 1, "Pointer Leave not dispatched on PointerCancel");
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ setTimeout(function() {
+ window.parent.postMessage("run next", "*");
+ }, 0);
+ }, 0);
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.implicit_capture", false]
+ ]
+ }, runTests);
+}
+
+</script>
+</pre>
+<div id="d0">
+Test divs --
+<div id="d1">t</div><div id="d2">t</div><div id="d3">t</div>
+--
+</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug970964_inner2.html b/layout/base/tests/bug970964_inner2.html
new file mode 100644
index 0000000000..51cee6c1ee
--- /dev/null
+++ b/layout/base/tests/bug970964_inner2.html
@@ -0,0 +1,356 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970964
+-->
+<head>
+ <title>Test for Bug 970964</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=970964">Mozilla Bug 970964</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 970964 **/
+
+function ok(condition, msg) {
+ parent.ok(condition, msg);
+}
+
+function is(a, b, msg) {
+ parent.is(a, b, msg);
+}
+
+function testtouch(aOptions) {
+ if (!aOptions)
+ aOptions = {};
+ this.identifier = aOptions.identifier || 0;
+ this.target = aOptions.target || 0;
+ this.page = aOptions.page || {x: 0, y: 0};
+ this.radius = aOptions.radius || {x: 0, y: 0};
+ this.rotationAngle = aOptions.rotationAngle || 0;
+ this.force = aOptions.force || 1;
+}
+
+function touchEvent(aOptions) {
+ if (!aOptions) {
+ aOptions = {};
+ }
+ this.ctrlKey = aOptions.ctrlKey || false;
+ this.altKey = aOptions.altKey || false;
+ this.shiftKey = aOptions.shiftKey || false;
+ this.metaKey = aOptions.metaKey || false;
+ this.touches = aOptions.touches || [];
+ this.targetTouches = aOptions.targetTouches || [];
+ this.changedTouches = aOptions.changedTouches || [];
+}
+
+function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
+ var ids = [], xs=[], ys=[], rxs = [], rys = [],
+ rotations = [], forces = [], tiltXs = [], tiltYs = [], twists = [];
+
+ for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
+ for (var i = 0; i < aEvent[touchType].length; i++) {
+ if (!ids.includes(aEvent[touchType][i].identifier)) {
+ ids.push(aEvent[touchType][i].identifier);
+ xs.push(aEvent[touchType][i].page.x);
+ ys.push(aEvent[touchType][i].page.y);
+ rxs.push(aEvent[touchType][i].radius.x);
+ rys.push(aEvent[touchType][i].radius.y);
+ rotations.push(aEvent[touchType][i].rotationAngle);
+ forces.push(aEvent[touchType][i].force);
+ tiltXs.push(0);
+ tiltYs.push(0);
+ twists.push(0);
+ }
+ }
+ }
+ return windowUtils.sendTouchEvent(aType,
+ ids, xs, ys, rxs, rys,
+ rotations, forces, tiltXs, tiltYs, twists,
+ aModifiers, 0);
+}
+
+function getDefaultArgEvent(eventname) {
+ return new PointerEvent(eventname, {
+ bubbles: true, cancelable: true, view: window,
+ detail: 0, screenX: 0, screenY: 0, clientX: 0, clientY: 0,
+ ctrlKey: false, altKey: false, shiftKey: false, metaKey: false,
+ button: 0, relatedTarget: null, pointerId: 0
+ });
+}
+
+function getTouchEventForTarget(target, cwu, id) {
+ var bcr = target.getBoundingClientRect();
+ var touch = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: id,
+ });
+ var event = new touchEvent({
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch]
+ });
+ return event;
+}
+
+function runTests() {
+ var d0 = document.getElementById("d0");
+ var d1 = document.getElementById("d1");
+ var d2 = document.getElementById("d2");
+ var d3 = document.getElementById("d3");
+
+ // Test Pointer firing before any mouse/touch original source
+
+ var mouseDownTriggered = 0;
+ var pointerDownTriggered = 0;
+ var touchDownTriggered = 0;
+ var touchCancelTriggered = 0;
+ var pointerCancelTriggered = 0;
+
+ // Test pointer event generated from mouse event
+ d0.addEventListener("mousedown", (e) => {
+ ++mouseDownTriggered;
+ is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointerdown", (e) => {
+ ++pointerDownTriggered;
+ is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ }, {once: true});
+
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ synthesizeMouse(d1, 3, 3, { type: "mousedown"});
+ synthesizeMouse(d1, 3, 3, { type: "mouseup"});
+
+ // Test pointer event generated from touch event
+ mouseDownTriggered = 0;
+ pointerDownTriggered = 0;
+
+ d0.addEventListener("touchstart", (e) => {
+ ++touchDownTriggered;
+ is(pointerDownTriggered, touchDownTriggered, "Touch event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("mousedown", (e) => {
+ ++mouseDownTriggered;
+ is(pointerDownTriggered , mouseDownTriggered, "Mouse event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointerdown", (e) => {
+ ++pointerDownTriggered;
+ is(pointerDownTriggered, touchDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ is(pointerDownTriggered, mouseDownTriggered + 1, "Pointer event must be triggered before mouse event!");
+ }, {once: true});
+
+ d0.addEventListener("touchcancel", (e) => {
+ ++touchCancelTriggered;
+ is(pointerCancelTriggered, touchCancelTriggered, "Touch cancel event must be triggered after pointer event!");
+ }, {once: true});
+
+ d0.addEventListener("pointercancel", function(ev) {
+ is(ev.pointerId, 0, "Correct default pointerId");
+ is(ev.bubbles, true, "bubbles should be true");
+ is(ev.cancelable, false, "pointercancel cancelable should be false ");
+ ++pointerCancelTriggered;
+ is(pointerCancelTriggered, touchCancelTriggered + 1, "Pointer event must be triggered before touch event!");
+ }, {once: true});
+
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+ var event1 = getTouchEventForTarget(d1, cwu, 0);
+ sendTouchEvent(cwu, "touchstart", event1, 0);
+ sendTouchEvent(cwu, "touchmove", event1, 0);
+ // Test Touch to Pointer Cancel
+ sendTouchEvent(cwu, "touchcancel", event1, 0);
+
+ // Check Pointer enter/leave from mouse generated event
+ var mouseEnterTriggered = 0;
+ var pointerEnterTriggered = 0;
+ d2.onpointerenter = function(e) {
+ pointerEnterTriggered = 1;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ is(mouseEnterTriggered, 0, "Pointer event must be triggered before mouse event!");
+ };
+ d2.onmouseenter = function(e) {
+ mouseEnterTriggered = 1;
+ is(pointerEnterTriggered , 1, "Mouse event must be triggered after pointer event!");
+ };
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ d2.onmouseenter = function(e) {}
+
+ // Test Multi Pointer enter/leave for pointers generated from Mouse and Touch at the same time
+ // Enter mouse and touch generated pointers to different elements
+ var d1enterCount = 0;
+ var d2enterCount = 0;
+ var d3enterCount = 0;
+ var d1leaveCount = 0;
+ var d2leaveCount = 0;
+ var d3leaveCount = 0;
+ var mousePointerEnterLeaveCount = 0;
+ var touchPointerEnterLeaveCount = 0;
+
+ var checkPointerType = function(pointerType) {
+ if (pointerType == "mouse") {
+ ++mousePointerEnterLeaveCount;
+ } else if (pointerType == "touch") {
+ ++touchPointerEnterLeaveCount;
+ }
+ };
+
+ d1.onpointerenter = function(e) {
+ ++d1enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerenter = function(e) {
+ ++d2enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerenter = function(e) {
+ ++d3enterCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d1.onpointerleave = function(e) {
+ ++d1leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d2.onpointerleave = function(e) {
+ ++d2leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+ d3.onpointerleave = function(e) {
+ ++d3leaveCount;
+ is(e.bubbles, false, "bubbles should be false");
+ is(e.cancelable, false, "cancelable should be false");
+ checkPointerType(e.pointerType);
+ };
+
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ sendTouchEvent(cwu, "touchstart", getTouchEventForTarget(d3, cwu, 3), 0);
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d3, cwu, 3), 0);
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 2, "Wrong mouse enterLeave count for!");
+
+ is(d1enterCount, 1, "Wrong enter count for! d1");
+ is(d2leaveCount, 1, "Wrong leave count for! d2");
+ is(d3enterCount, 1, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d3, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 4, "Wrong mouse enterLeave count for!");
+
+ is(d3leaveCount, 0, "Wrong leave count for! d3");
+ is(d1leaveCount, 1, "Wrong leave count for! d1");
+ is(d1enterCount, 1, "Wrong enter count for! d1");
+ is(d3enterCount, 2, "Wrong enter count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d2, cwu, 3), 0);
+ synthesizeMouse(d2, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 6, "Wrong mouse enterLeave count for!");
+
+ is(d1leaveCount, 1, "Wrong leave count for! d1");
+ is(d2enterCount, 1, "Wrong enter count for! d2");
+ is(d3leaveCount, 1, "Wrong leave count for! d3");
+
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 3), 0);
+ synthesizeMouse(d1, 3, 3, { type: "mousemove"});
+ is(touchPointerEnterLeaveCount, 1, "Wrong touch enterLeave count for!");
+ is(mousePointerEnterLeaveCount, 8, "Wrong mouse enterLeave count for!");
+
+ is(d2leaveCount, 2, "Wrong leave count for! d2");
+ is(d1enterCount, 2, "Wrong enter count for! d1");
+
+ // Test for pointer buttons when it generated from mousemove event
+ d1.onpointermove = function(e) {
+ is(e.buttons, 0, "Buttons must be 0 on pointer generated from mousemove");
+ is(e.button, -1, "Button must be -1 on pointer generated from mousemove when no buttons pressed");
+ is(e.pointerType, "mouse", "Pointer type must be mouse");
+ };
+ cwu.sendMouseEvent("mousemove", 4, 4, 0, 0, 0, false, 0, 0);
+
+ d1.onpointermove = function(e) {
+ is(e.buttons, 1, "Buttons must be 1 on pointermove generated from touch event");
+ is(e.button, -1, "Button must be -1 on pointermove generated from touch event");
+ is(e.pointerType, "touch", "Pointer type must be touch");
+ };
+ sendTouchEvent(cwu, "touchmove", getTouchEventForTarget(d1, cwu, 2), 0);
+
+ // Test for cancel trigger pointerOut (Touch Pointer must be at d1 now)
+ pointerCancelTriggered = 0;
+ var pointerOutTriggeredForCancelEvent = 0;
+ var pointerLeaveTriggeredForCancelEvent = 0;
+ d3.onpointerout = function(e) {
+ if (pointerOutTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerOutTriggeredForCancelEvent = 1;
+ };
+ d3.onpointerleave = function(e) {
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out must be dispatched bedore Pointer leave");
+ if (pointerLeaveTriggeredForCancelEvent == 0) {
+ is(e.pointerId, 3, "Wrong Pointer type, should be id from Touch event");
+ is(e.pointerType, "touch", "Wrong Pointer type, should be touch type");
+ } else {
+ is(e.pointerId, 0, "Wrong Pointer type, should be id from mouse event");
+ is(e.pointerType, "mouse", "Wrong Pointer type, should be mouse type");
+ }
+ pointerLeaveTriggeredForCancelEvent = 1;
+ }
+ sendTouchEvent(cwu, "touchcancel", getTouchEventForTarget(d1, cwu, 3), 0);
+ is(pointerOutTriggeredForCancelEvent, 1, "Pointer Out not dispatched on PointerCancel");
+ is(pointerLeaveTriggeredForCancelEvent, 1, "Pointer Leave not dispatched on PointerCancel");
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ setTimeout(function() {
+ window.parent.postMessage("finishTest", "*");
+ }, 0);
+ }, 0);
+}
+
+window.onload = function () {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.implicit_capture", true]
+ ]
+ }, runTests);
+}
+
+</script>
+</pre>
+<div id="d0">
+Test divs --
+<div id="d1">t</div><div id="d2">t</div><div id="d3">t</div>
+--
+</div>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_1.html b/layout/base/tests/bug977003_inner_1.html
new file mode 100644
index 0000000000..774bfb43e2
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_1.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094913
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098139
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1094913, 1098139</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target{ background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var test_send_got = 0;
+ var test_got_async = 0;
+ var test_got_type = "";
+ var test_got_primary = false;
+ var test_send_lost = 0;
+ var test_lost_async = 0;
+ var test_lost_type = "";
+ var test_lost_primary = false;
+
+ function DownHandler(event) {
+ logger("Receive event: " + event.type);
+ logger("Send setPointerCapture to target");
+ target.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_send_got++;
+ }
+ function GotPCHandler(event) {
+ logger("Receive event: " + event.type + "(" + event.pointerType + ")");
+ if(test_send_got)
+ test_got_async++;
+ test_got_type = event.pointerType;
+ test_got_primary = event.isPrimary;
+ logger("Send releasePointerCapture from target");
+ target.releasePointerCapture(event.pointerId);
+ logger("releasePointerCapture was executed");
+ test_send_lost++;
+ }
+ function LostPCHandler(event) {
+ logger("Received event: " + event.type + "(" + event.pointerType + ")");
+ if(test_send_lost)
+ test_lost_async++;
+ test_lost_type = event.pointerType;
+ test_lost_primary = event.isPrimary;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ target.addEventListener("pointerdown", DownHandler);
+ target.addEventListener("gotpointercapture", GotPCHandler);
+ target.addEventListener("lostpointercapture", LostPCHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_send_got, 1, "Part 1: gotpointercapture event should be sent once");
+ parent.is(test_got_async, 1, "Part 1: gotpointercapture event should be asynchronous");
+ parent.is(test_got_type, "mouse", "Part 1: gotpointercapture event should have pointerType mouse");
+ parent.is(test_got_primary, true, "Part 1: gotpointercapture event should have isPrimary as true");
+ parent.is(test_send_lost, 1, "Part 1: lostpointercapture event should be sent once");
+ parent.is(test_lost_async, 1, "Part 1: lostpointercapture event should be asynchronous");
+ parent.is(test_lost_type, "mouse", "Part 1: lostpointercapture event should have pointerType mouse");
+ parent.is(test_lost_primary, true, "Part 1: lostpointercapture event should have isPrimary as true");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 1</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094913">Mozilla Bug 1094913</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098139">Mozilla Bug 1098139</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_2.html b/layout/base/tests/bug977003_inner_2.html
new file mode 100644
index 0000000000..cf7e6d729d
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_2.html
@@ -0,0 +1,75 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ logger("set/release was executed");
+ test_down_got = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ listener.addEventListener("gotpointercapture", ListenerHandler);
+ listener.addEventListener("lostpointercapture", ListenerHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 2: pointerdown event should be received by target");
+ parent.is(test_listener, false, "Part 2: listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 2</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_3.html b/layout/base/tests/bug977003_inner_3.html
new file mode 100644
index 0000000000..2448b4ada8
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_3.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #mediator, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_mediator_got = false;
+ var test_mediator_lost = false;
+ var test_listener = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send pointerCapture to Mediator");
+ mediator.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_got = true;
+ }
+ function MediatorGotPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ logger("Try send setPointerCapture on listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Try send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ test_mediator_got = true;
+ }
+ function MediatorLostPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ test_mediator_lost = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ mediator.addEventListener("gotpointercapture", MediatorGotPCHandler);
+ mediator.addEventListener("lostpointercapture", MediatorLostPCHandler);
+ listener.addEventListener("gotpointercapture", ListenerHandler);
+ listener.addEventListener("lostpointercapture", ListenerHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/3, rect.height/3, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/4, rect.height/4, {type: "mousemove"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 3: pointerdown event should be received");
+ parent.is(test_mediator_got, true, "Part 3: gotpointercapture event should be received by Mediator");
+ parent.is(test_mediator_lost, true, "Part 3: lostpointercapture event should be received by Mediator");
+ parent.is(test_listener, false, "Part 3: listener should not receive any events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 3</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_4.html b/layout/base/tests/bug977003_inner_4.html
new file mode 100644
index 0000000000..2ea4a83568
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_4.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #mediator, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var mediator = undefined;
+ var listener = undefined;
+ var test_down_got = false;
+ var test_mediator_got = false;
+ var test_mediator_lost = false;
+ var test_listener_got = false;
+ var test_listener_lost = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send pointerCapture to Mediator");
+ mediator.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_got = true;
+ }
+ function MediatorGotPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ logger("Try send setPointerCapture on listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("Try send releasePointerCapture from Mediator");
+ mediator.releasePointerCapture(event.pointerId);
+ test_mediator_got = true;
+ }
+ function MediatorLostPCHandler(event) {
+ logger("Mediator receive event: " + event.type);
+ test_mediator_lost = true;
+ }
+ function ListenerGotHandler(event) {
+ test_listener_got = true;
+ }
+ function ListenerLostHandler(event) {
+ test_listener_lost = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ mediator = document.getElementById("mediator");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ mediator.addEventListener("gotpointercapture", MediatorGotPCHandler);
+ mediator.addEventListener("lostpointercapture", MediatorLostPCHandler);
+ listener.addEventListener("gotpointercapture", ListenerGotHandler);
+ listener.addEventListener("lostpointercapture", ListenerLostHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/3, rect.height/3, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/4, rect.height/4, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/4, rect.height/4, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_got, true, "Part 4: pointerdown event should be received");
+ parent.is(test_mediator_got, true, "Part 4: gotpointercapture event should be received by Mediator");
+ parent.is(test_mediator_lost, true, "Part 4: lostpointercapture event should be received by Mediator");
+ parent.is(test_listener_got, true, "Part 4: gotpointercapture event should be received by listener");
+ parent.is(test_listener_lost, true, "Part 4: lostpointercapture event should be received by listener");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 4</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="mediator">div id=mediator</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_5.html b/layout/base/tests/bug977003_inner_5.html
new file mode 100644
index 0000000000..73ca41acd3
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_5.html
@@ -0,0 +1,121 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1073563
+https://bugzilla.mozilla.org/show_bug.cgi?id=1094913
+https://bugzilla.mozilla.org/show_bug.cgi?id=1098139
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1073563, 1094913, 1098139</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_down_target = false;
+ var test_got_listener = false;
+ var test_got_type = "";
+ var test_got_primary = false;
+ var test_lost_listener = false;
+ var test_lost_type = "";
+ var test_move_listener = false;
+ var test_over_listener = false;
+ var test_listener = false;
+ var test_lost_primary = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("setPointerCapture was executed");
+ test_down_target = true;
+ }
+ function ListenerGotPCHandler(event) {
+ logger("Receive event on Listener: " + event.type + "(" + event.pointerType + ")");
+ listener.releasePointerCapture(event.pointerId);
+ test_got_listener = true;
+ test_got_type = event.pointerType;
+ test_got_primary = event.isPrimary;
+ }
+ function ListenerLostPCHandler(event) {
+ logger("Receive event on Listener: " + event.type + "(" + event.pointerType + ")");
+ test_lost_listener = true;
+ test_lost_type = event.pointerType;
+ test_lost_primary = event.isPrimary;
+ }
+ function ListenerMoveHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_move_listener = true;
+ }
+ function ListenerOverHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_over_listener = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ test_listener = true;
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ listener.addEventListener("gotpointercapture", ListenerGotPCHandler);
+ listener.addEventListener("lostpointercapture", ListenerLostPCHandler);
+ listener.addEventListener("pointerover", ListenerOverHandler);
+ listener.addEventListener("pointermove", ListenerMoveHandler);
+ listener.addEventListener("pointerup", ListenerHandler);
+ listener.addEventListener("pointerout", ListenerHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown", isPrimary: true, inputSource: MouseEvent.MOZ_SOURCE_TOUCH});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousemove", isPrimary: true, inputSource: MouseEvent.MOZ_SOURCE_TOUCH});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup", isPrimary: true, inputSource: MouseEvent.MOZ_SOURCE_TOUCH});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_down_target, true, "Part 5: pointerdown event should be received by target");
+ parent.is(test_got_listener, true, "Part 5: listener should receive gotpointercapture event");
+ parent.is(test_got_type, "touch", "Part 5: gotpointercapture event should have pointerType touch");
+ parent.is(test_got_primary, true, "Part 5: gotpointercapture event should have isPrimary as true");
+ parent.is(test_lost_listener, true, "Part 5: listener should receive lostpointercapture event");
+ parent.is(test_lost_type, "touch", "Part 5: lostpointercapture event should have pointerType touch");
+ parent.is(test_lost_primary, true, "Part 5: lostpointercapture event should have isPrimary as true");
+ parent.is(test_move_listener, true, "Part 5: gotpointercapture should be triggered by pointermove");
+ parent.is(test_over_listener, true, "Part 5: listener should receive pointerover when capturing pointer");
+ parent.is(test_listener, false, "Part 5: listener should not receive any other events");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 5</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1073563">Mozilla Bug 1073563</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1094913">Mozilla Bug 1094913</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1098139">Mozilla Bug 1098139</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug977003_inner_6.html b/layout/base/tests/bug977003_inner_6.html
new file mode 100644
index 0000000000..b4a05122a5
--- /dev/null
+++ b/layout/base/tests/bug977003_inner_6.html
@@ -0,0 +1,105 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+https://bugzilla.mozilla.org/show_bug.cgi?id=1073563
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bugs 977003, 1073563</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <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>
+ #target, #listener { background: yellow; }
+ </style>
+ <script type="application/javascript">
+ var target = undefined;
+ var listener = undefined;
+ var test_target = false;
+ var test_move = false;
+ var test_over = false;
+ var test_listener = false;
+ var receive_lostpointercapture = false;
+
+ function TargetDownHandler(event) {
+ logger("Target receive event: " + event.type);
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ logger("set/release was executed");
+ test_target = true;
+ }
+ function ListenerHandler(event) {
+ logger("Receive event on Listener: " + event.type);
+ if("gotpointercapture" == event.type) {
+ logger("Send releasePointerCapture from listener");
+ listener.releasePointerCapture(event.pointerId);
+ } else if(event.type == "lostpointercapture") {
+ // Set/release pointer capture in the event listeners of got/lostpointercapture won't take effect immediately
+ parent.is(receive_lostpointercapture, false, "Part 6: listener should receive only one lostpointercapture");
+ if (!receive_lostpointercapture) {
+ receive_lostpointercapture = true;
+ logger("Send setPointerCapture to listener");
+ listener.setPointerCapture(event.pointerId);
+ }
+ } else if(event.type == "pointermove") {
+ test_move = true;
+ } else if(event.type == "pointerover") {
+ test_over = true;
+ } else {
+ test_listener = true;
+ }
+ }
+ function logger(message) {
+ console.log(message);
+ var log = document.getElementById('log');
+ log.innerHTML = message + "<br>" + log.innerHTML;
+ }
+
+ function prepareTest() {
+ SimpleTest.executeSoon(executeTest);
+ }
+ function executeTest()
+ {
+ logger("executeTest");
+ target = document.getElementById("target");
+ listener = document.getElementById("listener");
+ target.addEventListener("pointerdown", TargetDownHandler);
+ listener.addEventListener("gotpointercapture", ListenerHandler);
+ listener.addEventListener("pointerdown", ListenerHandler);
+ listener.addEventListener("pointerover", ListenerHandler);
+ listener.addEventListener("pointermove", ListenerHandler);
+ listener.addEventListener("pointerup", ListenerHandler);
+ listener.addEventListener("pointerout", ListenerHandler);
+ listener.addEventListener("lostpointercapture", ListenerHandler);
+ var rect = target.getBoundingClientRect();
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousedown"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mousemove"});
+ synthesizeMouse(target, rect.width/2, rect.height/2, {type: "mouseup"});
+ finishTest();
+ }
+ function finishTest() {
+ parent.is(test_target, true, "Part 6: pointerdown event should be received by target");
+ // PE level 2 defines that the pending pointer capture is processed when firing next pointer events.
+ // In this test case, pointer capture release is processed when firing pointermove
+ parent.is(test_move, true, "Part 6: gotpointercapture should be triggered by pointermove");
+ parent.is(test_over, true, "Part 6: pointerover should be received when capturing pointer");
+ parent.is(test_listener, false, "Part 6: no other pointerevents should be fired before gotpointercapture except pointerover");
+ logger("finishTest");
+ parent.finishTest();
+ }
+ </script>
+</head>
+<body onload="prepareTest()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=977003">Mozilla Bug 977003 Test 6</a>
+ <br><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1073563">Mozilla Bug 1073563</a>
+ <p id="display"></p>
+ <div id="content" style="display: none">
+ </div>
+ <div id="listener">div id=listener</div>
+ <div id="target">div id=target</div>
+ <pre id="log">
+ </pre>
+</body>
+</html>
diff --git a/layout/base/tests/bug989012-1-ref.html b/layout/base/tests/bug989012-1-ref.html
new file mode 100644
index 0000000000..6802038089
--- /dev/null
+++ b/layout/base/tests/bug989012-1-ref.html
@@ -0,0 +1,21 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img alt="IMAGE">bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-1.html b/layout/base/tests/bug989012-1.html
new file mode 100644
index 0000000000..3d6b32854a
--- /dev/null
+++ b/layout/base/tests/bug989012-1.html
@@ -0,0 +1,24 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ </head>
+ <body onload="start()">
+ <div onfocus="setTimeout(done, 0)" contenteditable>foo<img alt="IMAGE">bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-2-ref.html b/layout/base/tests/bug989012-2-ref.html
new file mode 100644
index 0000000000..4b3072e7cd
--- /dev/null
+++ b/layout/base/tests/bug989012-2-ref.html
@@ -0,0 +1,26 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ span:before {
+ content: "IMAGE";
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<span></span>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-2.html b/layout/base/tests/bug989012-2.html
new file mode 100644
index 0000000000..7edb002ab2
--- /dev/null
+++ b/layout/base/tests/bug989012-2.html
@@ -0,0 +1,29 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ span:before {
+ content: "IMAGE";
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="setTimeout(done, 0)" contenteditable>foo<span></span>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-3-ref.html b/layout/base/tests/bug989012-3-ref.html
new file mode 100644
index 0000000000..9d637f536f
--- /dev/null
+++ b/layout/base/tests/bug989012-3-ref.html
@@ -0,0 +1,28 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ img {
+ border: solid 1px red;
+ mid-width: 1em;
+ display: inline-block;
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="done()" contenteditable>foo<img>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ // Set the caret right before "bar"
+ sel.collapse(div.lastChild, 0);
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/bug989012-3.html b/layout/base/tests/bug989012-3.html
new file mode 100644
index 0000000000..7448b052b9
--- /dev/null
+++ b/layout/base/tests/bug989012-3.html
@@ -0,0 +1,31 @@
+<html class="reftest-wait">
+ <head>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <style>
+ img {
+ border: solid 1px red;
+ mid-width: 1em;
+ display: inline-block;
+ }
+ </style>
+ </head>
+ <body onload="start()">
+ <div onfocus="setTimeout(done, 0)" contenteditable>foo<img>bar</div>
+ <script>
+ var div = document.querySelector("div");
+ function start() {
+ div.focus();
+ }
+ function done() {
+ var sel = getSelection();
+ sel.collapse(div, 0);
+ // Press Right four times to set the caret right before "bar"
+ for (var i = 0; i < 4; ++i) {
+ synthesizeKey("KEY_ArrowRight");
+ }
+ document.documentElement.removeAttribute("class");
+ }
+ </script>
+ </body>
+</html>
diff --git a/layout/base/tests/chrome/animated.gif b/layout/base/tests/chrome/animated.gif
new file mode 100644
index 0000000000..b2895487bd
--- /dev/null
+++ b/layout/base/tests/chrome/animated.gif
Binary files differ
diff --git a/layout/base/tests/chrome/blue-32x32.png b/layout/base/tests/chrome/blue-32x32.png
new file mode 100644
index 0000000000..deefd19b2a
--- /dev/null
+++ b/layout/base/tests/chrome/blue-32x32.png
Binary files differ
diff --git a/layout/base/tests/chrome/bug1041200_frame.html b/layout/base/tests/chrome/bug1041200_frame.html
new file mode 100644
index 0000000000..0030ec0edd
--- /dev/null
+++ b/layout/base/tests/chrome/bug1041200_frame.html
@@ -0,0 +1,2 @@
+<body onload='parent.childLoaded()' style='background:lime'>
+<p>Hello<p>Hello<p>Hello<p>Hello<p>Hello<p>
diff --git a/layout/base/tests/chrome/bug1041200_window.html b/layout/base/tests/chrome/bug1041200_window.html
new file mode 100644
index 0000000000..005f7bcd13
--- /dev/null
+++ b/layout/base/tests/chrome/bug1041200_window.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1041200</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+</head>
+<body>
+<iframe style="width:700px; height:500px; margin-top:200px;" id="ourFrame"></iframe>
+<script>
+var SpecialPowers = window.arguments[0].SpecialPowers;
+var SimpleTest = window.arguments[0].SimpleTest;
+var ok = window.arguments[0].ok;
+var info = window.arguments[0].info;
+
+var viewer = SpecialPowers.setFullZoom(window, 2);
+
+SimpleTest.waitForExplicitFinish();
+
+window.onload = function() {
+ window.waitForAllPaintsFlushed(function () {
+ // Supply random key to ensure load actually happens
+ ourFrame.src = "bug1041200_frame.html?" + Math.random();
+ }, document.getElementById("ourFrame").contentDocument);
+};
+
+window.childLoaded = function() {
+ setTimeout(function() {
+ window.waitForAllPaintsFlushed(function(x1, y1, x2, y2) {
+ // We set the full zoom of this window too, so we need to account for it here.
+ ok(x2 - x1 >= 700 / 2 && y2 - y1 >= 500 / 2,
+ "expected to see invalidate of entire frame, got " + [x1,y1,x2,y2].join(','));
+ SimpleTest.finish();
+ window.close();
+ });
+ }, 0);
+};
+</script>
+
diff --git a/layout/base/tests/chrome/bug1722890.html b/layout/base/tests/chrome/bug1722890.html
new file mode 100644
index 0000000000..76175eb33d
--- /dev/null
+++ b/layout/base/tests/chrome/bug1722890.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+div {
+ position: fixed;
+ width: 20px;
+ height: 20px;
+ background: blue;
+ left: 0;
+}
+ </style>
+ </head>
+ <body>
+ <div style="top:0"></div>
+ <div style="bottom:0"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/chrome/bug1722890_ref.html b/layout/base/tests/chrome/bug1722890_ref.html
new file mode 100644
index 0000000000..8bf8e39713
--- /dev/null
+++ b/layout/base/tests/chrome/bug1722890_ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <style>
+div {
+ position: fixed;
+ width: 10px;
+ height: 10px;
+ background: blue;
+ left: 0;
+}
+ </style>
+ </head>
+ <body>
+ <div style="top:0"></div>
+ <div style="bottom:0"></div>
+ </body>
+</html>
+
diff --git a/layout/base/tests/chrome/bug1769161_1.html b/layout/base/tests/chrome/bug1769161_1.html
new file mode 100644
index 0000000000..b1cc3b6ded
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_1.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 1in;
+ height: 1in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 20in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_1_ref.html b/layout/base/tests/chrome/bug1769161_1_ref.html
new file mode 100644
index 0000000000..ab56d8d045
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_1_ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 0.5in;
+ height: 0.5in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 10in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_2.html b/layout/base/tests/chrome/bug1769161_2.html
new file mode 100644
index 0000000000..7728d0bd95
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_2.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 2in;
+ height: 2in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 10in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_2_ref.html b/layout/base/tests/chrome/bug1769161_2_ref.html
new file mode 100644
index 0000000000..3916e26a17
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_2_ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 10in;
+ height: 10in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 50in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_3.html b/layout/base/tests/chrome/bug1769161_3.html
new file mode 100644
index 0000000000..b1cc3b6ded
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_3.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 1in;
+ height: 1in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 20in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_3_ref.html b/layout/base/tests/chrome/bug1769161_3_ref.html
new file mode 100644
index 0000000000..b786e1ad2b
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_3_ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 0.5in;
+ height: 0.5in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 10in;
+ margin: 2in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_4.html b/layout/base/tests/chrome/bug1769161_4.html
new file mode 100644
index 0000000000..7728d0bd95
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_4.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 2in;
+ height: 2in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 10in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug1769161_4_ref.html b/layout/base/tests/chrome/bug1769161_4_ref.html
new file mode 100644
index 0000000000..be2de41058
--- /dev/null
+++ b/layout/base/tests/chrome/bug1769161_4_ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<style>
+div {
+ width: 10in;
+ height: 10in;
+ background-color: blue;
+}
+body {
+ margin: 0;
+}
+@page {
+ size: 50in;
+ margin: 10in;
+}
+</style>
+<body>
+ <div></div>
+</body>
diff --git a/layout/base/tests/chrome/bug495648.rdf b/layout/base/tests/chrome/bug495648.rdf
new file mode 100644
index 0000000000..b7045aa70a
--- /dev/null
+++ b/layout/base/tests/chrome/bug495648.rdf
@@ -0,0 +1,214 @@
+<?xml version="1.0"?>
+<RDF:RDF xmlns:NS1="http://sitedelta.schierla.de/SD-rdf#"
+ xmlns:NC="http://home.netscape.com/NC-rdf#"
+ xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=19&amp;btnG=Suche&amp;meta="
+ NC:name="19 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Bag RDF:about="urn:root:bag">
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=1&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=2&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=3&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=4&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=5&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=6&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=7&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=8&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=9&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=10&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=11&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=12&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=13&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=14&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=15&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=16&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=17&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=18&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=19&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=20&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=21&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=22&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=23&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=24&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=25&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=26&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=27&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=28&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=29&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=30&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=31&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=32&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=33&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=34&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=35&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=36&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=37&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=38&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=39&amp;btnG=Suche&amp;meta="/>
+ <RDF:li RDF:resource="http://www.google.de/search?hl=de&amp;q=40&amp;btnG=Suche&amp;meta="/>
+ </RDF:Bag>
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=2&amp;btnG=Suche&amp;meta="
+ NC:name="2 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=12&amp;btnG=Suche&amp;meta="
+ NC:name="12 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=5&amp;btnG=Suche&amp;meta="
+ NC:name="5 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=6&amp;btnG=Suche&amp;meta="
+ NC:name="6 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=22&amp;btnG=Suche&amp;meta="
+ NC:name="22 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=17&amp;btnG=Suche&amp;meta="
+ NC:name="17 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="urn:root">
+ <NC:links RDF:resource="urn:root:bag"/>
+ </RDF:Description>
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=8&amp;btnG=Suche&amp;meta="
+ NC:name="8 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=1&amp;btnG=Suche&amp;meta="
+ NC:name="1 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=16&amp;btnG=Suche&amp;meta="
+ NC:name="16 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=21&amp;btnG=Suche&amp;meta="
+ NC:name="21 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=20&amp;btnG=Suche&amp;meta="
+ NC:name="20 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=9&amp;btnG=Suche&amp;meta="
+ NC:name="9 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=11&amp;btnG=Suche&amp;meta="
+ NC:name="11 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=14&amp;btnG=Suche&amp;meta="
+ NC:name="14 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=23&amp;btnG=Suche&amp;meta="
+ NC:name="23 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=24&amp;btnG=Suche&amp;meta="
+ NC:name="24 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=18&amp;btnG=Suche&amp;meta="
+ NC:name="18 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www0.pafnet.de/user/322033"
+ NC:name="pafnet - You2_xD"
+ NS1:nextScan="1243707104073"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=3&amp;btnG=Suche&amp;meta="
+ NC:name="3 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=7&amp;btnG=Suche&amp;meta="
+ NC:name="7 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=15&amp;btnG=Suche&amp;meta="
+ NC:name="15 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=4&amp;btnG=Suche&amp;meta="
+ NC:name="4 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=13&amp;btnG=Suche&amp;meta="
+ NC:name="13 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=10&amp;btnG=Suche&amp;meta="
+ NC:name="10 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=25&amp;btnG=Suche&amp;meta="
+ NC:name="25 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=26&amp;btnG=Suche&amp;meta="
+ NC:name="26 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=27&amp;btnG=Suche&amp;meta="
+ NC:name="27 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=28&amp;btnG=Suche&amp;meta="
+ NC:name="28 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=29&amp;btnG=Suche&amp;meta="
+ NC:name="29 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=30&amp;btnG=Suche&amp;meta="
+ NC:name="30 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=31&amp;btnG=Suche&amp;meta="
+ NC:name="31 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=32&amp;btnG=Suche&amp;meta="
+ NC:name="32 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=33&amp;btnG=Suche&amp;meta="
+ NC:name="33 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=34&amp;btnG=Suche&amp;meta="
+ NC:name="34 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=35&amp;btnG=Suche&amp;meta="
+ NC:name="35 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=36&amp;btnG=Suche&amp;meta="
+ NC:name="36 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=37&amp;btnG=Suche&amp;meta="
+ NC:name="37 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=38&amp;btnG=Suche&amp;meta="
+ NC:name="38 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=39&amp;btnG=Suche&amp;meta="
+ NC:name="39 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+ <RDF:Description RDF:about="http://www.google.de/search?hl=de&amp;q=40&amp;btnG=Suche&amp;meta="
+ NC:name="40 - Google-Suche"
+ NS1:nextScan="0"
+ NS1:status="0" />
+</RDF:RDF>
diff --git a/layout/base/tests/chrome/bug551434_childframe.html b/layout/base/tests/chrome/bug551434_childframe.html
new file mode 100644
index 0000000000..3d7bd6c13a
--- /dev/null
+++ b/layout/base/tests/chrome/bug551434_childframe.html
@@ -0,0 +1,4 @@
+<script>
+var gKeyDownChild = 0, gKeyPressChild = 0, gKeyUpChild = 0;
+</script>
+<input id='i4' onkeydown="gKeyDownChild++" onkeypress="gKeyPressChild++" onkeyup="gKeyUpChild++; this.parentNode.removeChild(this);">
diff --git a/layout/base/tests/chrome/chrome.toml b/layout/base/tests/chrome/chrome.toml
new file mode 100644
index 0000000000..6636b224e6
--- /dev/null
+++ b/layout/base/tests/chrome/chrome.toml
@@ -0,0 +1,167 @@
+[DEFAULT]
+prefs = [
+ "dom.window.sizeToContent.enabled=true",
+ "layout.css.individual-transform.enabled=true",
+]
+skip-if = ["os == 'android'"]
+support-files = [
+ "animated.gif",
+ "blue-32x32.png",
+ "bug1722890.html",
+ "bug1722890_ref.html",
+ "bug1769161_1.html",
+ "bug1769161_1_ref.html",
+ "bug1769161_2.html",
+ "bug1769161_2_ref.html",
+ "bug1769161_3.html",
+ "bug1769161_3_ref.html",
+ "bug1769161_4.html",
+ "bug1769161_4_ref.html",
+ "bug551434_childframe.html",
+ "chrome_content_integration_window.xhtml",
+ "default_background_window.xhtml",
+ "dialog_with_positioning_window.xhtml",
+ "file_bug458898.html",
+ "green.png",
+ "printpreview_bug1713404_ref.html",
+ "printpreview_bug1730091_ref.html",
+ "printpreview_bug396024_helper.xhtml",
+ "printpreview_bug482976_helper.xhtml",
+ "printpreview_helper.xhtml",
+ "printpreview_downloadable_font.html",
+ "printpreview_downloadable_font_ref.html",
+ "printpreview_downloadable_font_in_iframe.html",
+ "printpreview_downloadable_font_in_iframe_ref.html",
+ "printpreview_font_api.html",
+ "printpreview_font_api_ref.html",
+ "printpreview_font_mozprintcallback.html",
+ "printpreview_font_mozprintcallback_ref.html",
+ "printpreview_quirks.html",
+ "printpreview_quirks_ref.html",
+ "printpreview_images.html",
+ "printpreview_images_ref.html",
+ "printpreview_images_sw.html",
+ "printpreview_images_sw_ref.html",
+ "printpreview_images_sw.js",
+ "printpreview_image_select.html",
+ "printpreview_image_select_ref.html",
+ "printpreview_mixed_page_size_001.html",
+ "printpreview_mixed_page_size_002.html",
+ "printpreview_pps_uw2.html",
+ "printpreview_pps_uw2_ref.html",
+ "printpreview_pps_uw2_no_margin_ref.html",
+ "printpreview_pps_uw4.html",
+ "printpreview_pps_uw4_ref.html",
+ "printpreview_pps_uw9.html",
+ "printpreview_pps_uw9_ref.html",
+ "printpreview_pps2.html",
+ "printpreview_pps2_ref.html",
+ "printpreview_pps4.html",
+ "printpreview_pps4_ref.html",
+ "printpreview_pps6.html",
+ "printpreview_pps6_ref.html",
+ "printpreview_pps9.html",
+ "printpreview_pps9_ref.html",
+ "printpreview_pps16.html",
+ "printpreview_pps16_ref.html",
+ "printpreview_prettyprint.xml",
+ "printpreview_prettyprint_ref.xhtml",
+ "printpreview_mask.html",
+ "print_page_size1.html",
+ "print_page_size1_ref.html",
+ "print_page_size2.html",
+ "print_page_size2_ref.html",
+ "print_page_size3.html",
+ "print_page_size3_ref.html",
+ "print_page_size4.html",
+ "print_page_size4_ref.html",
+ "red.png",
+ "color_adjust.html",
+ "color_adjust_ref.html",
+ "test_document_adopted_styles.html",
+ "test_document_adopted_styles_ref.html",
+ "test_shadow_root_adopted_styles.html",
+ "test_shadow_root_adopted_styles_ref.html",
+ "test_shared_adopted_styles.html",
+ "test_shared_adopted_styles_ref.html",
+ "file_bug1018265.xhtml",
+ "markA.ttf",
+ "markB.ttf",
+]
+
+["test_bug396367-1.html"]
+
+["test_bug396367-2.html"]
+
+["test_bug420499.xhtml"]
+
+["test_bug458898.html"]
+
+["test_bug465448.xhtml"]
+support-files = ["file_bug465448.html"]
+
+["test_bug514660.xhtml"]
+
+["test_bug533845.xhtml"]
+skip-if = ["os == 'linux' && !debug"] # Bug 1208197
+
+["test_bug551434.html"]
+
+["test_bug708062.html"]
+
+["test_bug812817.xhtml"]
+
+["test_bug1018265.xhtml"]
+
+["test_bug1041200.xhtml"]
+skip-if = ["os == 'win' && bits == 64"] # Bug 1272321
+support-files = [
+ "bug1041200_frame.html",
+ "bug1041200_window.html",
+]
+
+["test_chrome_content_integration.xhtml"]
+
+["test_color_scheme_browser.xhtml"]
+
+["test_css_visibility_propagation.xhtml"]
+support-files = [
+ "window_css_visibility_propagation-1.xhtml",
+ "window_css_visibility_propagation-2.xhtml",
+ "window_css_visibility_propagation-3.html",
+ "window_css_visibility_propagation-4.html",
+ "frame_css_visibility_propagation.html",
+]
+
+["test_default_background.xhtml"]
+
+["test_dialog_with_positioning.html"]
+tags = "openwindow"
+
+["test_fixed_bg_scrolling_repaints.html"]
+
+["test_getClientRectsAndTexts.html"]
+
+["test_get_printer_basic_attributes.html"]
+
+["test_get_printer_orientation.html"]
+
+["test_get_printer_paper_sizes.html"]
+
+["test_prerendered_transforms.html"]
+
+["test_printer_default_settings.html"]
+
+["test_printpreview.xhtml"]
+skip-if = ["verify && os == 'win'"]
+
+["test_printpreview_bug396024.xhtml"]
+skip-if = ["verify && os == 'win'"]
+
+["test_printpreview_bug482976.xhtml"]
+skip-if = ["verify && os == 'win'"]
+
+["test_scrolling_repaints.html"]
+
+["test_will_change.html"]
+skip-if = ["true"]
diff --git a/layout/base/tests/chrome/chrome_content_integration_window.xhtml b/layout/base/tests/chrome/chrome_content_integration_window.xhtml
new file mode 100644
index 0000000000..1dd1b7f882
--- /dev/null
+++ b/layout/base/tests/chrome/chrome_content_integration_window.xhtml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<window title="Content/chrome integration subwindow"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="runTests()"
+ style="background:black; -moz-appearance:none;">
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <stack style="align-items: center; height: 300px; width: 200px;">
+ <!-- the bottom 100px is a strip of black that should be visible through the content iframe -->
+ <vbox style="background:pink; border-bottom:100px solid black"/>
+ <!-- the middle 100px is a strip of black in the content iframe -->
+ <!-- the bottom 100px of the iframe is transparent, the top 100px is yellow -->
+ <iframe type="content" style="border:none; width: 10px; height: 10px;"
+ transparent="transparent"
+ src="data:text/html,&lt;div style='position:absolute;left:0;top:0;width:100%;height:100px;background:yellow;border-bottom:100px solid black'&gt;"/>
+ <!-- the top 100px is a strip of black above the content iframe -->
+ <vbox style="border-top:100px solid black;"/>
+ </stack>
+
+ <script type="application/javascript">
+ <![CDATA[
+ var imports = [ "SimpleTest", "is", "isnot", "ok", "SpecialPowers" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function runTests() {
+ var testCanvas = snapshotWindow(window);
+
+ var refCanvas = snapshotWindow(window);
+ var ctx = refCanvas.getContext('2d');
+ ctx.fillStyle = "black";
+ ctx.fillRect(0, 0, refCanvas.width, refCanvas.height);
+
+ var comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Rendering OK, got " + comparison[1] + ", expected " + comparison[2]);
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/color_adjust.html b/layout/base/tests/chrome/color_adjust.html
new file mode 100644
index 0000000000..4a9846e28d
--- /dev/null
+++ b/layout/base/tests/chrome/color_adjust.html
@@ -0,0 +1,9 @@
+<!doctype html>
+<style>
+.test {
+ background-color: black;
+ color: white;
+ color-adjust: exact;
+}
+</style>
+<span class="test">Some text goes here</span>
diff --git a/layout/base/tests/chrome/color_adjust_ref.html b/layout/base/tests/chrome/color_adjust_ref.html
new file mode 100644
index 0000000000..f5986f93bf
--- /dev/null
+++ b/layout/base/tests/chrome/color_adjust_ref.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<style>
+.test {
+ background-color: black;
+ color: white;
+}
+</style>
+<span class="test">Some text goes here</span>
diff --git a/layout/base/tests/chrome/default_background_window.xhtml b/layout/base/tests/chrome/default_background_window.xhtml
new file mode 100644
index 0000000000..4bc49c0f36
--- /dev/null
+++ b/layout/base/tests/chrome/default_background_window.xhtml
@@ -0,0 +1,67 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?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"
+ onload="runTests()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
+
+ <iframe type="content" id="f" src="about:blank" style="border:1px solid black;"/>
+
+ <script>
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+
+ var imports = [ "SimpleTest", "is", "isnot", "ok" ];
+ for (var name of imports) {
+ window[name] = window.arguments[0][name];
+ }
+
+ function snapshot(win) {
+ var el = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ el.width = win.innerWidth;
+ el.height = win.innerHeight;
+
+ var ctx = el.getContext("2d");
+ ctx.drawWindow(win, 0, 0,
+ win.innerWidth, win.innerHeight,
+ "rgba(0,0,0,0)", 0);
+ return el;
+ }
+
+ var color = '#2468AC';
+ var prefs = Cc["@mozilla.org/preferences-service;1"]
+ .getService(Ci.nsIPrefBranch);
+ var backgroundPref = matchMedia('(prefers-color-scheme:dark)').matches
+ ? 'browser.display.background_color.dark'
+ : 'browser.display.background_color';
+ prefs.setCharPref(backgroundPref, color);
+ // On Windows, this preference is true by default, we make it
+ // false to ensure we're using the color set above for our reference.
+ prefs.setBoolPref('browser.display.use_system_colors', false);
+
+ function runTests() {
+ var f = document.getElementById("f");
+
+ var testCanvas = snapshot(f.contentWindow);
+ prefs.clearUserPref(backgroundPref);
+ // Reset sys colors pref so we're using the system color for our test.
+ prefs.clearUserPref('browser.display.use_system_colors');
+
+ var refCanvas = snapshot(f.contentWindow);
+ var ctx = refCanvas.getContext('2d');
+ ctx.fillStyle = color;
+ ctx.fillRect(0, 0, refCanvas.width, refCanvas.height);
+
+ var comparison = compareSnapshots(testCanvas, refCanvas, true);
+ ok(comparison[0], "Rendering OK, got " + comparison[1] + ", expected " + comparison[2]);
+
+
+ var tester = window.SimpleTest;
+ window.close();
+ tester.finish();
+ }
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/dialog_with_positioning_window.xhtml b/layout/base/tests/chrome/dialog_with_positioning_window.xhtml
new file mode 100644
index 0000000000..a854248b21
--- /dev/null
+++ b/layout/base/tests/chrome/dialog_with_positioning_window.xhtml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(runTest, 0)">
+ <vbox>
+ <label value="powered by example.com" style="padding: 16px;"/>
+ </vbox>
+ <hbox id="t" style="display: block; position: fixed; right: 16px; bottom: 16px;">
+ <button label="OK"/>
+ </hbox>
+<script><![CDATA[
+var SimpleTest = window.arguments[0].SimpleTest;
+var SpecialPowers = window.arguments[0].SpecialPowers;
+var is = window.arguments[0].is;
+var ok = window.arguments[0].ok;
+
+// We run this off a setTimeout from onload, because the XUL window
+// only does its intrinsic-height layout after the load event has
+// finished
+function runTest() {
+ var t = document.getElementById("t");
+ var tBottom = t.getBoundingClientRect().bottom;
+ is(tBottom, document.documentElement.getBoundingClientRect().bottom - 16,
+ "check fixed-pos element t bottom positioned correctly");
+ ok(tBottom < 200, "fixed-pos element t bottom must be sane, less than 200 (got " + tBottom + ")");
+ window.close();
+ SimpleTest.finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/file_bug1018265.xhtml b/layout/base/tests/chrome/file_bug1018265.xhtml
new file mode 100644
index 0000000000..1f9be6ba97
--- /dev/null
+++ b/layout/base/tests/chrome/file_bug1018265.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1018265
+-->
+<window title="Mozilla Bug 1018265"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="setTimeout(run, 0);">
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1018265 **/
+
+ var testcontent = null;
+
+ function run() {
+ testcontent = document.getElementById("testcontent");
+ shouldHaveTwoNonHiddenDocumentViewers();
+ testcontent.setAttribute("src", "foobarpage");
+ setTimeout(errorPageLoaded, 2500)
+ }
+
+ function errorPageLoaded() {
+ testcontent.addEventListener("pageshow", didGoBack, true);
+ setTimeout(function() {testcontent.contentWindow.history.back();}, 0);
+ }
+
+ function didGoBack(e) {
+ testcontent.removeEventListener("pageshow", didGoBack, true);
+ shouldHaveTwoNonHiddenDocumentViewers();
+ window.arguments[0].done();
+ window.close();
+ }
+
+ function getDocumentViewer(win) {
+ return win.docShell.docViewer;
+ }
+
+ function shouldHaveTwoNonHiddenDocumentViewers() {
+ window.arguments[0].is(getDocumentViewer(testcontent.contentWindow).isHidden, false, "Top level DocumentViewer should not be hidden.");
+ window.arguments[0].is(getDocumentViewer(testcontent.contentWindow.frames[0]).isHidden, false, " Iframe's DocumentViewer should not be hidden.");
+ }
+ ]]>
+ </script>
+
+ <browser type="content" id="testcontent" flex="1" src="data:text/html,&lt;iframe&gt;&lt;/iframe&gt;"/>
+</window>
diff --git a/layout/base/tests/chrome/file_bug458898.html b/layout/base/tests/chrome/file_bug458898.html
new file mode 100644
index 0000000000..6db840689d
--- /dev/null
+++ b/layout/base/tests/chrome/file_bug458898.html
@@ -0,0 +1 @@
+<div style='height:200px; width:100px;'>
diff --git a/layout/base/tests/chrome/file_bug465448.html b/layout/base/tests/chrome/file_bug465448.html
new file mode 100644
index 0000000000..5df5b61375
--- /dev/null
+++ b/layout/base/tests/chrome/file_bug465448.html
@@ -0,0 +1 @@
+<body onload='window.opener.loaded()'><div style='height:200px; width:100px;'>
diff --git a/layout/base/tests/chrome/frame_css_visibility_propagation.html b/layout/base/tests/chrome/frame_css_visibility_propagation.html
new file mode 100644
index 0000000000..dbb5d819d1
--- /dev/null
+++ b/layout/base/tests/chrome/frame_css_visibility_propagation.html
@@ -0,0 +1 @@
+<button id="button">Button</button>
diff --git a/layout/base/tests/chrome/green.png b/layout/base/tests/chrome/green.png
new file mode 100644
index 0000000000..25b76c3c6f
--- /dev/null
+++ b/layout/base/tests/chrome/green.png
Binary files differ
diff --git a/layout/base/tests/chrome/markA.ttf b/layout/base/tests/chrome/markA.ttf
new file mode 100644
index 0000000000..353e7ac332
--- /dev/null
+++ b/layout/base/tests/chrome/markA.ttf
Binary files differ
diff --git a/layout/base/tests/chrome/markB.ttf b/layout/base/tests/chrome/markB.ttf
new file mode 100644
index 0000000000..c683ddf945
--- /dev/null
+++ b/layout/base/tests/chrome/markB.ttf
Binary files differ
diff --git a/layout/base/tests/chrome/print_page_size1.html b/layout/base/tests/chrome/print_page_size1.html
new file mode 100644
index 0000000000..851b910f26
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 3in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 20px;
+ height: 20px;
+}
+.upper {
+ top: 1in;
+}
+.lower{
+ bottom: 1.5in;
+}
+.left{
+ left: 1in;
+}
+.right{
+ right: 1.5in;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size1_ref.html b/layout/base/tests/chrome/print_page_size1_ref.html
new file mode 100644
index 0000000000..cc3ff8598f
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size1_ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 2in;
+ margin: 0.5in;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 20px;
+ height: 20px;
+}
+.upper {
+ top: 0.5in;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0.5in;
+}
+.right{
+ right: 0;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size2.html b/layout/base/tests/chrome/print_page_size2.html
new file mode 100644
index 0000000000..d9a181576d
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size2.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 20in 3in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 20px;
+ height: 20px;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size2_ref.html b/layout/base/tests/chrome/print_page_size2_ref.html
new file mode 100644
index 0000000000..0a40acc0eb
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size2_ref.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 20in 5in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 20px;
+ height: 20px;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ /* This accounts for the difference in @page{ size: ... }, as long as the
+ * page is placed aligned to the upper left corner of the paper.
+ */
+ bottom: 2in;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size3.html b/layout/base/tests/chrome/print_page_size3.html
new file mode 100644
index 0000000000..45c9709817
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size3.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 3in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 1in;
+ height: 1in;
+}
+.upper {
+ top: 0in;
+}
+.lower{
+ bottom: 0in;
+}
+.left{
+ left: 0in;
+}
+.right{
+ right: 0in;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size3_ref.html b/layout/base/tests/chrome/print_page_size3_ref.html
new file mode 100644
index 0000000000..cc6f69a7ad
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size3_ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<style>
+@page {
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 1in;
+ height: 1in;
+}
+/* These positions assume the test case was placed in the upper left corner of
+ * the page, rather than being centered or scaled.
+ */
+.upper {
+ top: 0;
+}
+.lower{
+ top: 2in;
+}
+.left{
+ left: 0;
+}
+.right{
+ left: 2in;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size4.html b/layout/base/tests/chrome/print_page_size4.html
new file mode 100644
index 0000000000..ef6077e2a1
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size4.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 10in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 1in;
+ height: 1in;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/print_page_size4_ref.html b/layout/base/tests/chrome/print_page_size4_ref.html
new file mode 100644
index 0000000000..3d32134824
--- /dev/null
+++ b/layout/base/tests/chrome/print_page_size4_ref.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<style>
+@page {
+ size: 20in;
+ margin: 0;
+}
+div {
+ position: absolute;
+ background: blue;
+ width: 2in;
+ height: 2in;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<body>
+<div class="upper left"></div>
+<div class="upper right"></div>
+<div class="lower left"></div>
+<div class="lower right"></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_bug1713404_ref.html b/layout/base/tests/chrome/printpreview_bug1713404_ref.html
new file mode 100644
index 0000000000..a08074b8a2
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug1713404_ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<style>
+@page {
+ margin: 2in;
+}
+div {
+ /* The default header/footer size in Firefox. */
+ font-size: 10pt;
+ position: absolute;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<div class="upper left">|</div>
+<div class="upper right">||</div>
+<div class="lower left">|||</div>
+<div class="lower right">||||</div>
diff --git a/layout/base/tests/chrome/printpreview_bug1730091_ref.html b/layout/base/tests/chrome/printpreview_bug1730091_ref.html
new file mode 100644
index 0000000000..ac1577b852
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug1730091_ref.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<style>
+@page{
+ margin: 0;
+}
+div {
+ /* The default header/footer size in Firefox */
+ font-size: 10pt;
+ position: absolute;
+}
+.upper {
+ top: 0;
+}
+.lower{
+ bottom: 0;
+}
+.left{
+ left: 0;
+}
+.right{
+ right: 0;
+}
+</style>
+<div class="upper left">||||</div>
+<div class="upper right">||||</div>
+<div class="lower left">||||</div>
+<div class="lower right">||||</div>
diff --git a/layout/base/tests/chrome/printpreview_bug396024_helper.xhtml b/layout/base/tests/chrome/printpreview_bug396024_helper.xhtml
new file mode 100644
index 0000000000..fa587e9746
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug396024_helper.xhtml
@@ -0,0 +1,96 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396024
+-->
+<window title="Mozilla Bug 396024" onload="run()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<iframe id="i" src="about:blank" type="content"></iframe>
+<iframe src="about:blank" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
+var is = window.arguments[0].is;
+var ok = window.arguments[0].ok;
+var todo = window.arguments[0].todo;
+var SimpleTest = window.arguments[0].SimpleTest;
+var gWbp;
+function printpreview() {
+ gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+ let settings = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService).createNewPrintSettings();
+ gWbp.printPreview(settings, frameElts[0].contentWindow);
+}
+
+function exitprintpreview() {
+ frameElts[1].contentWindow.docShell.exitPrintPreview();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+function run()
+{
+/** Test for Bug 396024 **/
+ var printService = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService);
+
+ if (printService.lastUsedPrinterName != '') {
+ printpreview();
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore1");
+ printpreview();
+ setTimeout(run2, 0)
+ } else {
+ todo(false, "No printer seems installed on this machine, that is necessary for this test");
+ finish();
+ }
+}
+
+function run2() {
+ var loadhandler = function() {
+ document.getElementById("i").removeEventListener("load", arguments.callee, true);
+ setTimeout(run3, 0);
+ };
+ document.getElementById("i").addEventListener("load", loadhandler, true);
+ frameElts[0].contentWindow.location.reload();
+}
+
+function run3() {
+ gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ setTimeout(run4, 0);
+}
+
+function run4() {
+ var i = document.getElementById("i");
+ i.remove();
+ var loadhandler = function() {
+ document.getElementById("i").removeEventListener("load", loadhandler, true);
+ setTimeout(run5, 0);
+ };
+ i.addEventListener("load", loadhandler, true);
+ document.documentElement.getBoundingClientRect();
+ document.documentElement.prepend(i);
+}
+
+function run5() {
+ gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore2");
+
+ //XXX this shouldn't be necessary, see bug 405555
+ printpreview();
+ exitprintpreview();
+ finish(); //should not have crashed after all of this
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_bug482976_helper.xhtml b/layout/base/tests/chrome/printpreview_bug482976_helper.xhtml
new file mode 100644
index 0000000000..81c29e1588
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_bug482976_helper.xhtml
@@ -0,0 +1,50 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482976
+-->
+<window title="Mozilla Bug 482976" onload="run1()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<iframe src="about:blank" type="content"></iframe>
+<iframe src="about:blank" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
+var is = window.arguments[0].is;
+var ok = window.arguments[0].ok;
+var todo = window.arguments[0].todo;
+var SimpleTest = window.arguments[0].SimpleTest;
+var gWbp;
+var gPrintPreviewWin;
+function printpreview() {
+ let settings = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService).createNewPrintSettings();
+ gPrintPreviewWin = frameElts[0].contentWindow.printPreview(settings);
+ gWbp = gPrintPreviewWin.docShell.docViewer;
+ gWbp.QueryInterface(Ci.nsIWebBrowserPrint);
+}
+
+function exitprintpreview() {
+ gPrintPreviewWin.docShell.exitPrintPreview();
+ gPrintPreviewWin.close();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+async function run1() {
+ printpreview();
+ ok(gWbp.doingPrintPreview, "Should be doing print preview");
+ exitprintpreview();
+ ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore");
+ finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_downloadable_font.html b/layout/base/tests/chrome/printpreview_downloadable_font.html
new file mode 100644
index 0000000000..6e30c55f79
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_downloadable_font.html
@@ -0,0 +1,24 @@
+<!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">
+<link rel="help" href="https://drafts.csswg.org/css-fonts/#font-face-rule">
+<style type="text/css">
+
+@font-face {
+ font-family: "MarkA";
+ src: url(markA.ttf);
+}
+
+body { font-family: "MarkA"; }
+
+</style>
+</head>
+<body>
+
+<p>A</p>
+
+</body>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe.html b/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe.html
new file mode 100644
index 0000000000..edee6ecca2
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe.html
@@ -0,0 +1,25 @@
+<!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">
+<link rel="help" href="https://drafts.csswg.org/css-fonts/#font-face-rule">
+</head>
+<iframe style="width:300px; height: 300px; border: 0"
+ srcdoc="<!DOCTYPE HTML>
+ <html>
+ <style>
+ @font-face {
+ font-family: 'MarkA';
+ src: url(markA.ttf);
+ }
+
+ body { font-family: 'MarkA'; }
+ </style>
+ <body>
+ <p>A</p>
+ </body>
+ </html>">
+</iframe>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe_ref.html b/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe_ref.html
new file mode 100644
index 0000000000..0a2a50c665
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_downloadable_font_in_iframe_ref.html
@@ -0,0 +1,24 @@
+<!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">
+</head>
+<iframe style="width:300px; height: 300px; border: 0"
+ srcdoc="<!DOCTYPE HTML>
+ <html>
+ <style>
+ @font-face {
+ font-family: 'MarkB';
+ src: url(markB.ttf);
+ }
+
+ body { font-family: 'MarkB'; }
+ </style>
+ <body>
+ <p>B</p>
+ </body>
+ </html>">
+</iframe>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_downloadable_font_ref.html b/layout/base/tests/chrome/printpreview_downloadable_font_ref.html
new file mode 100644
index 0000000000..5967abb5ba
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_downloadable_font_ref.html
@@ -0,0 +1,23 @@
+<!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">
+<style type="text/css">
+
+@font-face {
+ font-family: "MarkB";
+ src: url(markB.ttf);
+}
+
+body { font-family: "MarkB"; }
+
+</style>
+</head>
+<body>
+
+<p>B</p>
+
+</body>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_font_api.html b/layout/base/tests/chrome/printpreview_font_api.html
new file mode 100644
index 0000000000..1f82d5f2d9
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_font_api.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ .test {
+ font-family: test, monospace;
+ }
+ </style>
+</head>
+<body>
+ <p class="test">lmnop</p>
+ <script>
+ const fontData = "data:font/opentype;base64,T1RUTwAJAIAAAwAQQ0ZGICjPfaIAAACcAAAgsU9TLzJlXAb2AAAhUAAAAGBjbWFwiDMtcQAAIbAAAAMoaGVhZKspT7wAACTYAAAANmhoZWEEjgGGAAAlEAAAACRobXR4Mq4AAAAAJTQAAABsbWF4cAAbUAAAACWgAAAABm5hbWUqpGR/AAAlqAAAAmRwb3N0//OzMwAAKAwAAAAgAQAEBAABAQEeTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWwAAQEBRPgbAPgcAfgdAvgeA/gfBB4KAB+Lix4KAB+LiwwH+1z72Pp4+lgFHQAAANkPHQAAAAAQHQAAAQ4RHQAAACAdAAAfthIABQEBDSA9WmBWZXJzaW9uIDAuMTFTZWUgb3JpZ2luYWwgbm90aWNlTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWxOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbE1lZGl1bQAAAAAAJAAlACgALAA0ADUAQgBDAEQARQBGAEgASgBLAE0ATgBPAFAAUQBTAFQAVQBWAFgAWgAAABsCAAEAAwEWAhgDmQUYBpsHQAh9CZoKiQwADO8PEhAOERkR5hPoFVkWCReIGGcZvhqCG9kdEh5mHm6LDhwCmxwAIBYcAoUcAq0VHP/iBhz/8hz/6hz/9xz/+Rz/8RwAAAgc//gcAAAc//EcAAMc//EcAAUIHP/VHAANHP/RHAAIHP/XHAAACBz/IRz/Rxz/Nhz/Dh8c/2UcAG0c/5gcAKMeHABHHAAAHABCHAAWHAA5HAArCBwAHhwAFhwAEBwAEhwAIBwALAgc/+IcABYFHP/OHP/FHP/kHP/pHP/WHP/uCBz/5Bz/9Bz/4Rz/+hz/4RwAAAgc/6Ic/8gcAEEcAG0fHAAAHACSHABIHACtHABbHABMCBwAJRwAHhwAJxwAEBwAKRwAAAgcAFEcADIc/8Qc/54fHAAAHP/zHP//HP/3HP/+HP/zCBwAIBz/+gUOHALSHP/SFhwAjBwChBUcAA4c//4cAA0c//4cAAQc//8IHAAgHP/7HAAKHP/4HAAAHP/oCBwAABz/9hz//Bz/7xz/+Bz/4Agc/4cc/kIFHP/xHP/NHP/wHP/xHP/PHP/6CBz/5wccAR4GHAD/HAC+HACpHADjHxwAqxz/kBwAZhz/Qx4c/t4GHADgHP+7FRwABxwAGxwAEBwACxwAIBwAAAgcAG8cADYc/8Uc/4YfHAAAHP+OHP/eHP+JHP/KHP+zCBz/yxz/tBz/uRz/2xz/oRwAAAgc/9sc/+8cAAscABcfHAAAHAAMHAADHAAQHAAJHAAaCBwAAhwAARwAAhwAAB4OHALSHAAVFhwCrRwBShUc/uEGHP/mBxwAKxz//BwACBz//hwACxz/+ggcAAYc//wcAAYc//UcAAAc//cIHAAAHP/mHP/8HP/vHP/lHP+kCBz/6Rz/shwAABwAABz//Bz/+wgc//Ic/+8c/9cc//Mc/9kcAAAIHP+WHP/GHABCHAB6HxwAABwAlRwARBwApxwAWxwATQgcACYcACAcACscABAcAC0cAAAIHAAvHAAAHAAnHP/tHAAWHP/gCBwAFhz/3xwABxz/4xwAAhz/vAgcAB0c//wFHAAzHADdBRz/4QYc//Uc/+oc//Uc//gc/+wcAAAIHP/3HAAAHP/4HAACHP/tHAAHCBz/0xwADxz/3xwABhz/0hwAAAgc/7QcAAAc/7kc/+4c/8Mc/90IHP98HP+zHP+pHP9pHAAAHP9nCBz/YRwAeRz/khwArx4cAE0cAAAcAGQcABQcADkcABoIHAAcHAANBRwAMBwAtQUcABMcAEYcAAccAAgcADUcAAQIDhwCmxz/6xYcAZ0cAY4VHADYHADGBRwAKxwAJxwACBwAAxwAKxwABggcABkHHP8tBhz/5wccAAkc//8cAAkc//8cAAMcAAAIHAAZHP/9HAAKHP/5HAAAHP/xCBwAABz/4xz/vBz/vBz/Mhz/UAgcAD4cAOQFHAAQHAA0HAATHAAPHAA7HAAFCBwAGQcc/soGHP/nBxwADhz//hwADBz//hwABRz//wgcAB8c//wcAAsc//ccAAAc/+oIHAAAHP/1HP/8HP/rHP/5HP/mCBz/hhz+PwUc//Ac/8sc//Ec//Mc/88c//oIHP/nBxwBIQYcABkHHP/MHAAEHP/yHAAJHAAAHAAhCBwAABwABhwAARwABhwAARwABQgcAEMcAPkFHABxHP8OBRwACRz/7BwABBz/9BwAABz/9QgcAAAc//Ic//Qc//gc/+Yc//4IHP/8HP//HP/1HP//HP/0HP//CBz/5wccARgGHAAZBxz/2hwABBz/8hwABxz/9RwAGAgOHAIsHAACFhwAABz/7hUcAB4GHAAMHAAdHAAHHAAGHAATHAAACBwAChwAABwADRz//RwAFxz/+AgcADEc/+8cACIc//kcACgcAAAIHACMHABZHABPHAB8HxwAABwAVhz/0hwAQxz/hhwAXQgc/8IcAC8c//EcABYcAAAcAC0IHABAHAAoHAApHABAHhwAUBwAABwAJxz/zxwADRz/iwgcABsc//wFHAAoHADJBRz/4gYc//cc/+4c//Ic//gc/+scAAAIHP/3HAAAHP/vHAAEHP/nHAAHCBz/1RwADhz/5RwABRz/5BwAAAgc/44c/6kc/6wc/5IfHAAAHP/lHAAFHP/qHAAIHP/vCBwAFhz/1hwAKRz/1BwAOBz/1AgcAE4c/8QcACMc/9EcAAAc/9IIHAAAHP/qHP/5HP/oHP/0HP/qCBz/6Rz/2Rz/3Rz/7Rz/zxwAAAgc/8wcAAAc/9IcABkc/+ccACoIHP/tHAAfHP/4HAAeHP/8HAA8CBz/4xwAAgUOHAJjHAAyFhwCWBwCnRUc/dkGHP/aHP9VBRwAGRz/+QUcADQcAGQcAD8cACkcAGYcAAIIHP9sHP3nBRz/8hz/zRz/5hz/6xz/zRwAAAgc//IGHP/nBxwBSwYcABkHHP+6HAADHP/zHAAGHAAAHAAgCBwAABwADxwABBwAFRwABxwAGggcAIwcAfoFHABeHP//HAAnHP/VHAAFHP+RCBwAGxz//gUOHAH0HP/rFhwBxxwAfxUc/9cc/8kc//Ec//Ec//AcAAAIHP/5HP/7HAAGHAAJHxwAABwAGRwAChwAKhwAGBwAUQgcAEccAOwFHP+RHP/5BRz/7hz/xQUc//ccADMc/+ocABUc/9UcAAAIHP+FHP9qHP9AHP9kHxz/tBwAKxz/zRwAQR4cAD0cAAAcAC4cACUcADkcAF8IHP/1HP/ZHP/9HP/yHAAAHP/yCBz/2xwAHhz/4xwAJR4cAC8cAAAcAC8cACccADkcAFcIHP9DHAE0FRwAFhz//hwADxz/7hwAABz/5QgcAAAc/8Uc/94c/48c/9oc/7wIHP/lHP/QHP/iHP/lHP/lHAAACBz/5hz/7RwAFxwAHx8cAAAcADQcACEcAGQcACkcAEoIHAAeHAA1HAAhHAAfHAAbHP/+CA4cAfQc//IWHABaHAKCFRwAMRwADhz/+Rz/6R8cAAAc//Mc//Mc/8sc/+Yc/6UIHP+iHP65BRz/9xz/4Bz/9Rz/1BwAABz/+wgc/+QcAEkc/+AcAEEeHACjHACdHACsHACzHxwASRz/0hwAMxz/vh4c/80cAAAc/9wc/+gc/80c/70IHABaHAFIBRz/uhz/8xz/zxz/+Rz/pxz/9ggcAMcc/u0VHAAdHAAPHP/pHP/SHxwAABz/xRz/5Rz/oxz/3Bz/wggc/94c/8Qc/9kc/+Ec/9UcAAAIHP/uHP/zHAAMHAAPHxwAABwACRwAEhwAUhwACBwAIAgcAAscACccABccAD8cAA8cACMIHAAaHAA5HAAgHAAeHAAhHAAACA4cAbwc//sWHAFDHACNFRz/0Bz/vRz/4Rz/6Rz/1RwAAAgc/9Uc/+IcACQcADYfHAAAHAA+HAAaHABaHAAkHABBCBwAGhwALxwAHhwAGBwAHhwAAAgcAAwcAAoc//kc//YfHAAAHP/8HP/+HP/6HP/6HP/3CBz/9xz/8Bz//Bz/9RwAABz/9Agc/+IcABkc/+ocACEeHAAkHAAaHAAdHAApHxwANRz/0hwAJRz/vh4c/3Mc/3Ac/1wc/2AfHP+nHAA/HP/CHABaHhwALBwAABwAKxwAEBwAIhwAHQgcABocABYcABAcABMcACMcADIIDhwB9Bz/6xYcAcAcAIMVHP/lHP/THP/nHP/lHP/xHAAACBz/+xz/+hwABhwABh8cAAAcAAkcABYcAFccACMcAH8IHABvHAGVBRz/xBz/8hz/zxz/+Rz/nBz/+Agc/+UHHAAVBhwAGhwADxz/9hz/7h8cAAAc//cc//oc/+cc/+kc/6wIHP/uHP+8BRz/6BwAGBz/7RwACRz/4xwAAAgc/4Yc/2oc/z8c/2MfHP+4HAAtHP/LHAA9HhwAPxwAABwALhwAJBwAORwAXwgc//gc/9sc//0c//EcAAAc//EIHP/ZHAAWHP/pHAAmHhwAMRwAABwALRwAJhwANxwAWAgc/04cATEVHAAWHP//HAAPHP/rHAAAHP/kCBwAABz/1hz/1Bz/fBz/3hz/wQgc/+Qc/88c/+Mc/+cc/+McAAAIHP/nHAAAHP/uHAAcHAACHAAjCBwAAxwANhwAHxwAXBwAJBwAQQgcACAcADkcACQcACAcAB4c//4IDhwBvBwABRYcATgcAI4VHP/RHP+7HP/kHP/qHP/WHAAACBz/1Rz/6hwAHBwANh8cAAAcABAcAAIcAA0cAAQcABQIHABoHAAUHAAxHAAWHAAyHAAuCBwAIxwAIBwAExwAJhwAABwAIwgcADMc/9QcACQc/8MeHP9wHP9wHP9dHP9bHxz/rBwAQBz/wRwAVx4cAEwcAAAcADYcACccADwcAGMIHP86HABZFRwAJBwAihwALhwATRwAMBwAAAgcABMcAAkc//Qc/+ofHAAAHP+0HP/QHP+/HP+zHP/jCBz/+Rz//hz/8Rz/+xz/9Rz//AgOHAH0HP/MFhwCEhwBrRUc/5EGHP/fHAAYHP/hHAAJHP/SHAAACBz/hRz/nRz/rxz/mx8cAAAc/80cABoc/9wcADYc/+kIHP+vHP/SHP/xHP/yHAAAHP/bCBwAABz/4RwAEhz/7BwALBz/8Qgc/8Ic//Ec/+wc//gc/+kc/+0IHP/vHP/yHP/1HP/oHAAAHP/oCBz/vxwASRz/1xwAcx4cAIwcAGMcAD4cAFgfHAAAHAA8HP/YHAAiHP+THAAfCBz/yxwADwUc/+AcAAkc/+0cAA4cAAAcAA8IHAAQHAAPHAAUHAANHhwABRwAABwABxz//xwACBz//QgcAAsc//0cAAkc//8cAAscAAAIHAAsHAAAHAAsHAAMHAAnHAAWCBwAOhwAIRwAHxwAMxwAABwAPAgcAAAcABAc//8cAAoc//scABAIHABDBhz+lRz+hRUcAAsc//8cAE8c/+YcABQc//YIHAAcHP/zHAANHP/uHAAAHP/mCBz/1Rz/1Bz/5hz/tB4c/78c/9IcACAcAC4fHAAAHAAUHAAJHAAQHAAVHAAUCBwADBwADBwAHxwAEhwABxz//wgcAIwcAbUVHAAaHAATHP/nHP/eHxwAABz/3hz/9Bz/zhz/7xz/3Agc/+oc/9Ic/+cc/+oc/+IcAAAIHP/kHP/xHAAVHAAmHxwAABwAKBwAERwAPRwAFRwAJggcABMcACEcABUcABAcABocAAAIDhwBFhwAAhYcANYcAI0VHP/yHP/sBRz/5hz/2Rz/6Rz/6xz/8RwAAAgc//gc//kcAAccAAgfHAAAHAAGHAAGHAAiHAADHAAMCBwAWxwBTgUc/8oc//Qc/7oc//Yc/7Ic//oIHP/lBxwAKxwAEBz/+Bz/6x8cAAAc//gc//0c//Ec//wc/+8IHP/GHP8pBRz/+Bz/5Bz/+xz/5BwAABz/8wgc/9scABwc/+YcACkeHAA8HAAAHAAlHAAfHABGHABpCBz/1BwCLhUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+AcAB8c/9seDhwBFhz/QxYcANwcAZcVHAAyHAAMHP/7HP/mHxwAABz/9Bz/+xz/5xz/+Rz/4wgc/6Qc/p0FHP/mHP+cHP/vHP/hHP/kHAAACBz/9Rz/8xwABxwABR8cAAAcAAIcAAEcAAIcAAEcAAIIHAALHAAQHAACHAAFHAAAHAALCBwAGxz/6hwAFhz/5R4c/+Yc/+kc/+gc/+MfHP/SHAArHP/hHAA+HhwAZBwAABwARRwATBwAKBwAmwgcAHIcAbYFHP/FHP/zHP/bHP/7HP+QHP/2CBwAsxwA+xUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+EcAB8c/9oeDhwBFhwAAhYcANYcAI0VHP/YHP/FHP/rHP/rHP/vHAAACBz/+Bz/+RwABxwACB8cAAAcAA4cAAgcACIcAA4cADQIHACRHAILBRz/qhz/7xz/0Bz/+hz/tRz/+Qgc/+UHHAALHAABHAAGHAAAHAAEHAAACBwAGxwADxz/9hz/7h8cAAAc//Mc//Ic/8Ic/+wc/70IHP+2HP74BRz/6hz/tBz/9Bz/xhwAABz/6Agc/9wcABsc/+gcACgeHAA+HAAAHAAmHAAfHABFHABpCA4cAwoc//IWHAAwHAGXFRwACAYcACAcAA4c//gc/+4fHAAAHP/yHP/4HP/eHP/lHP+hCBz/vRz/EgUcAHkGHAA1HAC+HAAmHABWHAA4HABDCBwAFhwAGRwAHRwAFRwADRwAAAgcAAocAAkc//Yc//MfHAAAHP/xHP/0HP/WHP/fHP+aCBz/7xz/zBz/8xz/1hz/3Rz/jwgcAHgGHAA7HADAHAAGHAASHAAkHAA/CBwAKhwAShwAKBwAKhwAHhwAAAgcAAwcAAsc//Yc//UfHAAAHP/6HP/9HP/1HP/8HP/zCBz/0xz/fQUc/+oc/8Ec//Ic/8IcAAAc/+QIHP/XHAAYHP/qHAArHhwAPhwAABwAKBwAIRwAOhwAYggc/+ocAA0FHP/7HP/4HP/7HP/5HP/+HP/9CBz/6Rz/3Bz/6xz/6hz/8xwAAAgc//cc//kcAAccAAcfHAAAHAALHAAAHAAAHAAVHABDCBwALRwAhQUcAA8cACscAAgcACYcAAAcABoIHAAoHP/fHAAfHP/UHhz/vBwAABz/0hz/2Bz/rBz/fAgcABMcADIcAAccABocAAAcABwIHAAqHP/nHAAaHP/WHhz/5BwAABz/4hz/9Rz/5Bz/6Qgc/9sc/+Qc/+Ic/9oc/78c/5wIHABAHADHBRz/wBz/8Rz/6xz//Rz/jRz/9wgOHAIsHP/6FhwB3RwAhxUc/9gc/8Ic//Mc//Ic/+8cAAAIHP/4HP/6HAAHHAAKHxwAABwAChwABxwAFxwAEhwANggcACQcAG0FHAAQHAAuHAAKHAAtHAAAHAAYCBwAMBz/5hwAGxz/0R4c/9scAAAc/9wc//Ec/+Qc/+YIHP/cHP/dHP/tHP/oHP+9HP+dCBwAQBwAxgUc/8Ac//Ic/7Ac//Uc/8gc//4IHP/lBxwAKhz//xwADBz/+xwAABz/7QgcAAAc//Uc//Qc/9Ec/94c/4gIHP/lHP+gBRz/9Bz/0hz/+Rz/5xz/9hz/2wgcAHkGHAAvHACtHAAkHABXHAA+HABRCBwAFBwAGxwAHxwAFhwAEhwAAAgcAAwcAA0c//Uc//YfHAAAHP/9HP/+HP/4HP/9HP/2CBz/yRz/WgUc//Ac/9Ac//Qc/8ccAAAc/+YIHP/bHAAaHP/pHAAqHhwAOxwAABwAKhwAIhwAOhwAYQgOHAH0HP/9FhwBHxwBzhUc/20c/3Qc/2Ec/1gfHP+rHABDHP/BHABcHhwAlRwAiBwAmxwAqR8cAFgc/74cAD8c/6UeHP/3HP/jFRwAHRwAEhz/6hz/3B8cAAAc/7wc/+Ec/34c/+Ac/7oIHP/jHP/CHP/iHP/jHP/dHAAACBz/4hz/7RwAGBwAJh8cAAAcAE4cACUcAJIcACMcAD4IHAAaHAAuHAAdHAAXHAAgHAAACA4cAfQc/4gWHACPHAGXFRwALRz//hwACRz//BwAABz/7QgcAAAc//cc//cc/9gc//Mc/88IHP+cHP58BRz/8Bz/wxz/9Bz/8xz/1xwAAAgc//oGHP/lBxwA+QYcABsHHP/PHAABHP/wHAAIHAAAHAAYCBwAABwADBwACBwAIRwAEhwARAgcAAMcAAocAAMcAAscAAIcAAcIHAADHAANBRwAIxz/7hwADBz//BwAFRwAAAgcAIccAI4cALUcAK0fHABKHP/WHAAvHP++Hhz/xxwAABz/1Bz/4Bz/xRz/rQgcACYcAHMFHP+aHP/wHP/ZHP/6HP/MHP/6CBwBAxz/1RUcABoc//4cAA8c/+kc//4c/+AIHP/8HP+/HP/gHP+cHP/cHP+/CBz/4Rz/yhz/3xz/5Bz/3xwAAAgc/+oc/+8cABAcABQfHAAAHAAQHAAIHAAeHAAbHABbCBwAGBwAUhwAChwAHBwAEBwAGAgcABkcACccACEcABkcABoc//4IDhwBhRz/6xYcADAcAZcVHAAtHP/+HAAJHP/8HAAAHP/tCBwAABz/5xz/3hz/gBz/vBz/GwgcAHkGHAAPHAAuHAAOHAArHAAEHAAPCBwAIBwAZRwADxwAJhwAHRwAMAgcABocAC0cABYcABkcAA0cAAAIHAAEHAAAHAAFHP/8HAAHHP/3CBwADxz/7xwADRz/+BwAEBwAAAgcACIcABkcAB8cACofHAAoHP/pHAAaHP/eHhz/zRwAABz/1xz/zRz/sRz/YggcAEIcANEFHP/EHP/xHP/tHP/9HP+HHP/2CA4cAYUc/+0WHAFgHAHNFRz/4wYc//Uc/+0c//0c//4c//EcAAAIHP/3HAAAHP/3HAACHP/uHAAHCBz/5xwAChz/8xwAAxz/7BwAAAgc/68c/8sc/84c/7QfHAAAHP/KHAAOHP/jHABCHP+yCBwAJhz/0xwAEhz/3hwAABz/5wgc/+Ic/+cc/+gc/+AeHP/pHAAAHP/rHAALHP/wHAAUCBz/7BwAGRz/+BwAFxz/+BwANggc/+UcAAMFHP/qHP9aBRwAGwYcAAQcAAwcAAwcAAgcAA0cAAAIHAAHHAAAHAALHP/9HAANHP/7CBwAGBz/+BwAExz//BwAFBwAAAgcAFQcAEAcADkcAEsfHAAAHAAtHP/nHAAxHP/IHABBCBz/2hwALRz/7hwAHxwAABwAFggcACAcABUcABUcACAeHAAuHAAAHAAaHP/cHAAQHP+tCBwAGxz//gUOHAEWHP/1FhwBJBwBwRUc/7gGHAAnHACRBRz/3QYc/8oc/68c/84c/9cc/7Ec/+IIHP/dBxwAMwYc/7wc/xIFHP/yHP/NHP/2HP/THAAAHP/vCBz/2RwAHBz/5hwAKh4cADwcAAAcACgcACEcAEIcAGcIHP/qHAAOBRz/3hz/yxz/5hz/5Rz/8BwAAAgc//gc//gcAAgcAAcfHAAAHAASHAASHABJHAAkHAB6CBwABRwAERwACxwAKBwAERwAPQgcAFMGDhwCLBwADxYcAcgcAIUVHP/dHP/LHP/sHP/rHP/vHAAACBz/+Bz/+hwACBwACR8cAAAcAAocAAAcAAAcABkcAF8IHABOHAEMBRz/igYc/8gc/z8c/+Mc/74c/8cc/7gIHP/hHP/aHP/qHP/vHP/sHAAACBz/8hz/+RwACBwAER8cAAAcAA8cAAIcAAgcAA0cACsIHABeHAE0BRz/+hz//xz/+hz//xz/8Bz//Qgc/7Yc//Mc/8Uc//gc/9Ac//4IHP/lBxwALhz//RwAChz/+xwAABz/7AgcAAAc//Ac//sc/+Qc//cc/+MIHP/YHP97BRz/8hz/0Bz/+Rz/3BwAABz/6Agc/88cABoc/+ccADEeHABDHAAAHAAeHAAaHABnHACQCBz/7hz/yRz/+hz/5RwAABz/5Agc/9gcABUc/+wcACweHAA8HAAAHAAxHAAoHAA0HABZCA4cApscABAWHAGXBBwAGRwAABwACRz//xwACBz/+QgcABQc/+4cAA4c/6McAAMc/3YIHAACHP+yBRwAABz//BwAABz//xz//xz/3Qgc//8c/9MFHAAbBhwAORwAVgUcAAYcAAgcACwcAE0cACwcAE8IHAAJHAAQHAACHAAEHAAIHAAPCBwAFxz+4wUcABsGHAC5HADEHABXHACFHAAAHABUCBwAIRz/4xwAHRz/4B4c/+Ic/+Yc/+Qc/+AfHAAAHP/yHAAGHP/yHAAPHP/rCBwADhz/7RwABhz/8xwAABz/9ggcAAAc/+Ic/+Ic/9Ic/6cc/5cIHP/gHAFMBRz/5QYc/6wc/28c//Ac/+Mc/8gc/5sIHP/9HAB9HP/5HABAHP/pHABWCBz/1xz/9xz/4Rz/+xz/rRz/8ggOHAG8HP+iFhwAbBwBmxUcABIcAAAcAAUc//8cAAcc//wIHAARHP/2HAAQHP/XHAAPHP+5CBwAIRz/YBwAERz/nxwAABz/4QgcAAAc/+kc//cc/+gc/+4c/+cIHP/rHP/kHP/lHP/rHP/vHAAACBz/+RwAABz/7RwABxz/+BwABwgc/+8cAAwc/+ccAAkc/+4cAAAIHP/mHP/oHP/mHP/jHxz/3xwAGxz/5RwAIx4cADccAAAcAEEcACgcADccAEIIHACCHACfHAB3HADtHAAAHABlCBwAIhz/4xwAHhz/3x4c/+Ic/+Uc/+Uc/+EfHAAAHP/oHAAHHP/0HAAYHP/uCBwAEhz/8hwABhz/+BwAABz/8wgcAAAc/+Ic/+4c/9Yc/7sc/3sIHP/0HABLBRz/6hwAdhz/5hwAcBz/7hwALwgc/9Ac//Qc/9wc//oc/78c//oIDhwA+hwAfRYOHgoDliX/DAmmCvcMC6aRjpKWlZSdDAyLDA4dAAAAIBMAawEBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2wLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwAAAAAAAwIkAfQABQAAAooCuwAAAIwCigK7AAAB3wAxAQIAAAAABgAAAAAAAACAAACvUAAgSgAAAAAAAAAAKjIxKgABACD7BAPE/rwAZAPEAUQAAAAAAAAAAAHOArAAAAAgAAMAAAABAAMAAQAAAAwABAMcAAAATgBAAAUADgB+AKwA/wExAUIBUwFhAXgBfgGSAscC3QPAIBQgGiAeICIgJiAwIDogRCCsISIhJiICIgYiDyISIhoiHiIrIkgiYCJlJcrgBva++wT//wAAACAAoQCuATEBQQFSAWABeAF9AZICxgLYA8AgEyAYIBwgICAmIDAgOSBEIKwhIiEmIgIiBiIPIhEiGiIeIisiSCJgImQlyuAA9r77AP//AAAAAAAA/s8AAAAAAAD+iAAA/m4AAAAA/EAAAAAAAAAAAN/a39AAAN+831Te3t7a3f7d+t3xAADd5t3i3dXduN2gAADaNgAACUIAAAABAE4BCgEgAAABwAHCAcQAAAHEAAABxAHGAAABzgHQAdQB2AAAAAAB2AAAAAAAAAAAAAAAAAAAAcwAAAAAAAAAAAAAAcQAAAHEAAABzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAMAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAUABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAgACQAKAAsAAAAMAAAADQAOAAAADwAQABEAEgATAAAAFAAVABYAFwAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAXw889QAAA+gAAAAAngt+JwAAAACeC34nAAD+vA//A8QAAgARAAAAAAAAAAAAAQAAA8T+vAAA//8AAAAAAAACsADHAAAAAAAAAAAAAAAAABsAAAAAApsAAALSAAAC0gAAApsAAAIsAAACYwAAAfQAAAH0AAABvAAAAfQAAAG8AAAB9AAAARYAAAEWAAABFgAAAwoAAAIsAAAB9AAAAfQAAAGFAAABhQAAARYAAAIsAAACmwAAAbwAAAD6AAAAAFAAABsAAAAAABQA9gABAAAAAAAAABAAAAABAAAAAAABAB0AEAABAAAAAAACAAcALQABAAAAAAADAAgANAABAAAAAAAEAB0APAABAAAAAAAFAAwAWQABAAAAAAAGAAAAZQABAAAAAAAHAAcAZQABAAAAAAAIAAcAbAABAAAAAAAJAAcAcwADAAEECQAAACAAegADAAEECQABADoAmgADAAEECQACAA4A1AADAAEECQADABAA4gADAAEECQAEADoA8gADAAEECQAFABgBLAADAAEECQAGAAABRAADAAEECQAHAA4BRAADAAEECQAIAA4BUgADAAEECQAJAA4BYE9yaWdpbmFsIGxpY2VuY2VOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbFVua25vd251bmlxdWVJRE5XQkpaTCtOaW1idXNSb21ObzlMLU1lZGlJdGFsVmVyc2lvbiAwLjExVW5rbm93blVua25vd25Vbmtub3duAE8AcgBpAGcAaQBuAGEAbAAgAGwAaQBjAGUAbgBjAGUATgBXAEIASgBaAEwAKwBOAGkAbQBiAHUAcwBSAG8AbQBOAG8AOQBMAC0ATQBlAGQAaQBJAHQAYQBsAFUAbgBrAG4AbwB3AG4AdQBuAGkAcQB1AGUASQBEAE4AVwBCAEoAWgBMACsATgBpAG0AYgB1AHMAUgBvAG0ATgBvADkATAAtAE0AZQBkAGkASQB0AGEAbABWAGUAcgBzAGkAbwBuACAAMAAuADEAMQBVAG4AawBuAG8AdwBuAFUAbgBrAG4AbwB3AG4AVQBuAGsAbgBvAHcAbgADAAD/8LMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+
+ let testFontFace = new FontFace('test', 'url(' + fontData + ')');
+ document.fonts.add(testFontFace);
+ testFontFace.loaded.then(() => {
+ window.postMessage("ready", "*");
+ }).catch((e) => {
+ window.postMessage("error", "*");
+ });
+ </script>
+</body>
+</html> \ No newline at end of file
diff --git a/layout/base/tests/chrome/printpreview_font_api_ref.html b/layout/base/tests/chrome/printpreview_font_api_ref.html
new file mode 100644
index 0000000000..61b052b4a6
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_font_api_ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ @font-face {
+ font-family: test;
+ src: url(data:font/opentype;base64,T1RUTwAJAIAAAwAQQ0ZGICjPfaIAAACcAAAgsU9TLzJlXAb2AAAhUAAAAGBjbWFwiDMtcQAAIbAAAAMoaGVhZKspT7wAACTYAAAANmhoZWEEjgGGAAAlEAAAACRobXR4Mq4AAAAAJTQAAABsbWF4cAAbUAAAACWgAAAABm5hbWUqpGR/AAAlqAAAAmRwb3N0//OzMwAAKAwAAAAgAQAEBAABAQEeTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWwAAQEBRPgbAPgcAfgdAvgeA/gfBB4KAB+Lix4KAB+LiwwH+1z72Pp4+lgFHQAAANkPHQAAAAAQHQAAAQ4RHQAAACAdAAAfthIABQEBDSA9WmBWZXJzaW9uIDAuMTFTZWUgb3JpZ2luYWwgbm90aWNlTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWxOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbE1lZGl1bQAAAAAAJAAlACgALAA0ADUAQgBDAEQARQBGAEgASgBLAE0ATgBPAFAAUQBTAFQAVQBWAFgAWgAAABsCAAEAAwEWAhgDmQUYBpsHQAh9CZoKiQwADO8PEhAOERkR5hPoFVkWCReIGGcZvhqCG9kdEh5mHm6LDhwCmxwAIBYcAoUcAq0VHP/iBhz/8hz/6hz/9xz/+Rz/8RwAAAgc//gcAAAc//EcAAMc//EcAAUIHP/VHAANHP/RHAAIHP/XHAAACBz/IRz/Rxz/Nhz/Dh8c/2UcAG0c/5gcAKMeHABHHAAAHABCHAAWHAA5HAArCBwAHhwAFhwAEBwAEhwAIBwALAgc/+IcABYFHP/OHP/FHP/kHP/pHP/WHP/uCBz/5Bz/9Bz/4Rz/+hz/4RwAAAgc/6Ic/8gcAEEcAG0fHAAAHACSHABIHACtHABbHABMCBwAJRwAHhwAJxwAEBwAKRwAAAgcAFEcADIc/8Qc/54fHAAAHP/zHP//HP/3HP/+HP/zCBwAIBz/+gUOHALSHP/SFhwAjBwChBUcAA4c//4cAA0c//4cAAQc//8IHAAgHP/7HAAKHP/4HAAAHP/oCBwAABz/9hz//Bz/7xz/+Bz/4Agc/4cc/kIFHP/xHP/NHP/wHP/xHP/PHP/6CBz/5wccAR4GHAD/HAC+HACpHADjHxwAqxz/kBwAZhz/Qx4c/t4GHADgHP+7FRwABxwAGxwAEBwACxwAIBwAAAgcAG8cADYc/8Uc/4YfHAAAHP+OHP/eHP+JHP/KHP+zCBz/yxz/tBz/uRz/2xz/oRwAAAgc/9sc/+8cAAscABcfHAAAHAAMHAADHAAQHAAJHAAaCBwAAhwAARwAAhwAAB4OHALSHAAVFhwCrRwBShUc/uEGHP/mBxwAKxz//BwACBz//hwACxz/+ggcAAYc//wcAAYc//UcAAAc//cIHAAAHP/mHP/8HP/vHP/lHP+kCBz/6Rz/shwAABwAABz//Bz/+wgc//Ic/+8c/9cc//Mc/9kcAAAIHP+WHP/GHABCHAB6HxwAABwAlRwARBwApxwAWxwATQgcACYcACAcACscABAcAC0cAAAIHAAvHAAAHAAnHP/tHAAWHP/gCBwAFhz/3xwABxz/4xwAAhz/vAgcAB0c//wFHAAzHADdBRz/4QYc//Uc/+oc//Uc//gc/+wcAAAIHP/3HAAAHP/4HAACHP/tHAAHCBz/0xwADxz/3xwABhz/0hwAAAgc/7QcAAAc/7kc/+4c/8Mc/90IHP98HP+zHP+pHP9pHAAAHP9nCBz/YRwAeRz/khwArx4cAE0cAAAcAGQcABQcADkcABoIHAAcHAANBRwAMBwAtQUcABMcAEYcAAccAAgcADUcAAQIDhwCmxz/6xYcAZ0cAY4VHADYHADGBRwAKxwAJxwACBwAAxwAKxwABggcABkHHP8tBhz/5wccAAkc//8cAAkc//8cAAMcAAAIHAAZHP/9HAAKHP/5HAAAHP/xCBwAABz/4xz/vBz/vBz/Mhz/UAgcAD4cAOQFHAAQHAA0HAATHAAPHAA7HAAFCBwAGQcc/soGHP/nBxwADhz//hwADBz//hwABRz//wgcAB8c//wcAAsc//ccAAAc/+oIHAAAHP/1HP/8HP/rHP/5HP/mCBz/hhz+PwUc//Ac/8sc//Ec//Mc/88c//oIHP/nBxwBIQYcABkHHP/MHAAEHP/yHAAJHAAAHAAhCBwAABwABhwAARwABhwAARwABQgcAEMcAPkFHABxHP8OBRwACRz/7BwABBz/9BwAABz/9QgcAAAc//Ic//Qc//gc/+Yc//4IHP/8HP//HP/1HP//HP/0HP//CBz/5wccARgGHAAZBxz/2hwABBz/8hwABxz/9RwAGAgOHAIsHAACFhwAABz/7hUcAB4GHAAMHAAdHAAHHAAGHAATHAAACBwAChwAABwADRz//RwAFxz/+AgcADEc/+8cACIc//kcACgcAAAIHACMHABZHABPHAB8HxwAABwAVhz/0hwAQxz/hhwAXQgc/8IcAC8c//EcABYcAAAcAC0IHABAHAAoHAApHABAHhwAUBwAABwAJxz/zxwADRz/iwgcABsc//wFHAAoHADJBRz/4gYc//cc/+4c//Ic//gc/+scAAAIHP/3HAAAHP/vHAAEHP/nHAAHCBz/1RwADhz/5RwABRz/5BwAAAgc/44c/6kc/6wc/5IfHAAAHP/lHAAFHP/qHAAIHP/vCBwAFhz/1hwAKRz/1BwAOBz/1AgcAE4c/8QcACMc/9EcAAAc/9IIHAAAHP/qHP/5HP/oHP/0HP/qCBz/6Rz/2Rz/3Rz/7Rz/zxwAAAgc/8wcAAAc/9IcABkc/+ccACoIHP/tHAAfHP/4HAAeHP/8HAA8CBz/4xwAAgUOHAJjHAAyFhwCWBwCnRUc/dkGHP/aHP9VBRwAGRz/+QUcADQcAGQcAD8cACkcAGYcAAIIHP9sHP3nBRz/8hz/zRz/5hz/6xz/zRwAAAgc//IGHP/nBxwBSwYcABkHHP+6HAADHP/zHAAGHAAAHAAgCBwAABwADxwABBwAFRwABxwAGggcAIwcAfoFHABeHP//HAAnHP/VHAAFHP+RCBwAGxz//gUOHAH0HP/rFhwBxxwAfxUc/9cc/8kc//Ec//Ec//AcAAAIHP/5HP/7HAAGHAAJHxwAABwAGRwAChwAKhwAGBwAUQgcAEccAOwFHP+RHP/5BRz/7hz/xQUc//ccADMc/+ocABUc/9UcAAAIHP+FHP9qHP9AHP9kHxz/tBwAKxz/zRwAQR4cAD0cAAAcAC4cACUcADkcAF8IHP/1HP/ZHP/9HP/yHAAAHP/yCBz/2xwAHhz/4xwAJR4cAC8cAAAcAC8cACccADkcAFcIHP9DHAE0FRwAFhz//hwADxz/7hwAABz/5QgcAAAc/8Uc/94c/48c/9oc/7wIHP/lHP/QHP/iHP/lHP/lHAAACBz/5hz/7RwAFxwAHx8cAAAcADQcACEcAGQcACkcAEoIHAAeHAA1HAAhHAAfHAAbHP/+CA4cAfQc//IWHABaHAKCFRwAMRwADhz/+Rz/6R8cAAAc//Mc//Mc/8sc/+Yc/6UIHP+iHP65BRz/9xz/4Bz/9Rz/1BwAABz/+wgc/+QcAEkc/+AcAEEeHACjHACdHACsHACzHxwASRz/0hwAMxz/vh4c/80cAAAc/9wc/+gc/80c/70IHABaHAFIBRz/uhz/8xz/zxz/+Rz/pxz/9ggcAMcc/u0VHAAdHAAPHP/pHP/SHxwAABz/xRz/5Rz/oxz/3Bz/wggc/94c/8Qc/9kc/+Ec/9UcAAAIHP/uHP/zHAAMHAAPHxwAABwACRwAEhwAUhwACBwAIAgcAAscACccABccAD8cAA8cACMIHAAaHAA5HAAgHAAeHAAhHAAACA4cAbwc//sWHAFDHACNFRz/0Bz/vRz/4Rz/6Rz/1RwAAAgc/9Uc/+IcACQcADYfHAAAHAA+HAAaHABaHAAkHABBCBwAGhwALxwAHhwAGBwAHhwAAAgcAAwcAAoc//kc//YfHAAAHP/8HP/+HP/6HP/6HP/3CBz/9xz/8Bz//Bz/9RwAABz/9Agc/+IcABkc/+ocACEeHAAkHAAaHAAdHAApHxwANRz/0hwAJRz/vh4c/3Mc/3Ac/1wc/2AfHP+nHAA/HP/CHABaHhwALBwAABwAKxwAEBwAIhwAHQgcABocABYcABAcABMcACMcADIIDhwB9Bz/6xYcAcAcAIMVHP/lHP/THP/nHP/lHP/xHAAACBz/+xz/+hwABhwABh8cAAAcAAkcABYcAFccACMcAH8IHABvHAGVBRz/xBz/8hz/zxz/+Rz/nBz/+Agc/+UHHAAVBhwAGhwADxz/9hz/7h8cAAAc//cc//oc/+cc/+kc/6wIHP/uHP+8BRz/6BwAGBz/7RwACRz/4xwAAAgc/4Yc/2oc/z8c/2MfHP+4HAAtHP/LHAA9HhwAPxwAABwALhwAJBwAORwAXwgc//gc/9sc//0c//EcAAAc//EIHP/ZHAAWHP/pHAAmHhwAMRwAABwALRwAJhwANxwAWAgc/04cATEVHAAWHP//HAAPHP/rHAAAHP/kCBwAABz/1hz/1Bz/fBz/3hz/wQgc/+Qc/88c/+Mc/+cc/+McAAAIHP/nHAAAHP/uHAAcHAACHAAjCBwAAxwANhwAHxwAXBwAJBwAQQgcACAcADkcACQcACAcAB4c//4IDhwBvBwABRYcATgcAI4VHP/RHP+7HP/kHP/qHP/WHAAACBz/1Rz/6hwAHBwANh8cAAAcABAcAAIcAA0cAAQcABQIHABoHAAUHAAxHAAWHAAyHAAuCBwAIxwAIBwAExwAJhwAABwAIwgcADMc/9QcACQc/8MeHP9wHP9wHP9dHP9bHxz/rBwAQBz/wRwAVx4cAEwcAAAcADYcACccADwcAGMIHP86HABZFRwAJBwAihwALhwATRwAMBwAAAgcABMcAAkc//Qc/+ofHAAAHP+0HP/QHP+/HP+zHP/jCBz/+Rz//hz/8Rz/+xz/9Rz//AgOHAH0HP/MFhwCEhwBrRUc/5EGHP/fHAAYHP/hHAAJHP/SHAAACBz/hRz/nRz/rxz/mx8cAAAc/80cABoc/9wcADYc/+kIHP+vHP/SHP/xHP/yHAAAHP/bCBwAABz/4RwAEhz/7BwALBz/8Qgc/8Ic//Ec/+wc//gc/+kc/+0IHP/vHP/yHP/1HP/oHAAAHP/oCBz/vxwASRz/1xwAcx4cAIwcAGMcAD4cAFgfHAAAHAA8HP/YHAAiHP+THAAfCBz/yxwADwUc/+AcAAkc/+0cAA4cAAAcAA8IHAAQHAAPHAAUHAANHhwABRwAABwABxz//xwACBz//QgcAAsc//0cAAkc//8cAAscAAAIHAAsHAAAHAAsHAAMHAAnHAAWCBwAOhwAIRwAHxwAMxwAABwAPAgcAAAcABAc//8cAAoc//scABAIHABDBhz+lRz+hRUcAAsc//8cAE8c/+YcABQc//YIHAAcHP/zHAANHP/uHAAAHP/mCBz/1Rz/1Bz/5hz/tB4c/78c/9IcACAcAC4fHAAAHAAUHAAJHAAQHAAVHAAUCBwADBwADBwAHxwAEhwABxz//wgcAIwcAbUVHAAaHAATHP/nHP/eHxwAABz/3hz/9Bz/zhz/7xz/3Agc/+oc/9Ic/+cc/+oc/+IcAAAIHP/kHP/xHAAVHAAmHxwAABwAKBwAERwAPRwAFRwAJggcABMcACEcABUcABAcABocAAAIDhwBFhwAAhYcANYcAI0VHP/yHP/sBRz/5hz/2Rz/6Rz/6xz/8RwAAAgc//gc//kcAAccAAgfHAAAHAAGHAAGHAAiHAADHAAMCBwAWxwBTgUc/8oc//Qc/7oc//Yc/7Ic//oIHP/lBxwAKxwAEBz/+Bz/6x8cAAAc//gc//0c//Ec//wc/+8IHP/GHP8pBRz/+Bz/5Bz/+xz/5BwAABz/8wgc/9scABwc/+YcACkeHAA8HAAAHAAlHAAfHABGHABpCBz/1BwCLhUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+AcAB8c/9seDhwBFhz/QxYcANwcAZcVHAAyHAAMHP/7HP/mHxwAABz/9Bz/+xz/5xz/+Rz/4wgc/6Qc/p0FHP/mHP+cHP/vHP/hHP/kHAAACBz/9Rz/8xwABxwABR8cAAAcAAIcAAEcAAIcAAEcAAIIHAALHAAQHAACHAAFHAAAHAALCBwAGxz/6hwAFhz/5R4c/+Yc/+kc/+gc/+MfHP/SHAArHP/hHAA+HhwAZBwAABwARRwATBwAKBwAmwgcAHIcAbYFHP/FHP/zHP/bHP/7HP+QHP/2CBwAsxwA+xUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+EcAB8c/9oeDhwBFhwAAhYcANYcAI0VHP/YHP/FHP/rHP/rHP/vHAAACBz/+Bz/+RwABxwACB8cAAAcAA4cAAgcACIcAA4cADQIHACRHAILBRz/qhz/7xz/0Bz/+hz/tRz/+Qgc/+UHHAALHAABHAAGHAAAHAAEHAAACBwAGxwADxz/9hz/7h8cAAAc//Mc//Ic/8Ic/+wc/70IHP+2HP74BRz/6hz/tBz/9Bz/xhwAABz/6Agc/9wcABsc/+gcACgeHAA+HAAAHAAmHAAfHABFHABpCA4cAwoc//IWHAAwHAGXFRwACAYcACAcAA4c//gc/+4fHAAAHP/yHP/4HP/eHP/lHP+hCBz/vRz/EgUcAHkGHAA1HAC+HAAmHABWHAA4HABDCBwAFhwAGRwAHRwAFRwADRwAAAgcAAocAAkc//Yc//MfHAAAHP/xHP/0HP/WHP/fHP+aCBz/7xz/zBz/8xz/1hz/3Rz/jwgcAHgGHAA7HADAHAAGHAASHAAkHAA/CBwAKhwAShwAKBwAKhwAHhwAAAgcAAwcAAsc//Yc//UfHAAAHP/6HP/9HP/1HP/8HP/zCBz/0xz/fQUc/+oc/8Ec//Ic/8IcAAAc/+QIHP/XHAAYHP/qHAArHhwAPhwAABwAKBwAIRwAOhwAYggc/+ocAA0FHP/7HP/4HP/7HP/5HP/+HP/9CBz/6Rz/3Bz/6xz/6hz/8xwAAAgc//cc//kcAAccAAcfHAAAHAALHAAAHAAAHAAVHABDCBwALRwAhQUcAA8cACscAAgcACYcAAAcABoIHAAoHP/fHAAfHP/UHhz/vBwAABz/0hz/2Bz/rBz/fAgcABMcADIcAAccABocAAAcABwIHAAqHP/nHAAaHP/WHhz/5BwAABz/4hz/9Rz/5Bz/6Qgc/9sc/+Qc/+Ic/9oc/78c/5wIHABAHADHBRz/wBz/8Rz/6xz//Rz/jRz/9wgOHAIsHP/6FhwB3RwAhxUc/9gc/8Ic//Mc//Ic/+8cAAAIHP/4HP/6HAAHHAAKHxwAABwAChwABxwAFxwAEhwANggcACQcAG0FHAAQHAAuHAAKHAAtHAAAHAAYCBwAMBz/5hwAGxz/0R4c/9scAAAc/9wc//Ec/+Qc/+YIHP/cHP/dHP/tHP/oHP+9HP+dCBwAQBwAxgUc/8Ac//Ic/7Ac//Uc/8gc//4IHP/lBxwAKhz//xwADBz/+xwAABz/7QgcAAAc//Uc//Qc/9Ec/94c/4gIHP/lHP+gBRz/9Bz/0hz/+Rz/5xz/9hz/2wgcAHkGHAAvHACtHAAkHABXHAA+HABRCBwAFBwAGxwAHxwAFhwAEhwAAAgcAAwcAA0c//Uc//YfHAAAHP/9HP/+HP/4HP/9HP/2CBz/yRz/WgUc//Ac/9Ac//Qc/8ccAAAc/+YIHP/bHAAaHP/pHAAqHhwAOxwAABwAKhwAIhwAOhwAYQgOHAH0HP/9FhwBHxwBzhUc/20c/3Qc/2Ec/1gfHP+rHABDHP/BHABcHhwAlRwAiBwAmxwAqR8cAFgc/74cAD8c/6UeHP/3HP/jFRwAHRwAEhz/6hz/3B8cAAAc/7wc/+Ec/34c/+Ac/7oIHP/jHP/CHP/iHP/jHP/dHAAACBz/4hz/7RwAGBwAJh8cAAAcAE4cACUcAJIcACMcAD4IHAAaHAAuHAAdHAAXHAAgHAAACA4cAfQc/4gWHACPHAGXFRwALRz//hwACRz//BwAABz/7QgcAAAc//cc//cc/9gc//Mc/88IHP+cHP58BRz/8Bz/wxz/9Bz/8xz/1xwAAAgc//oGHP/lBxwA+QYcABsHHP/PHAABHP/wHAAIHAAAHAAYCBwAABwADBwACBwAIRwAEhwARAgcAAMcAAocAAMcAAscAAIcAAcIHAADHAANBRwAIxz/7hwADBz//BwAFRwAAAgcAIccAI4cALUcAK0fHABKHP/WHAAvHP++Hhz/xxwAABz/1Bz/4Bz/xRz/rQgcACYcAHMFHP+aHP/wHP/ZHP/6HP/MHP/6CBwBAxz/1RUcABoc//4cAA8c/+kc//4c/+AIHP/8HP+/HP/gHP+cHP/cHP+/CBz/4Rz/yhz/3xz/5Bz/3xwAAAgc/+oc/+8cABAcABQfHAAAHAAQHAAIHAAeHAAbHABbCBwAGBwAUhwAChwAHBwAEBwAGAgcABkcACccACEcABkcABoc//4IDhwBhRz/6xYcADAcAZcVHAAtHP/+HAAJHP/8HAAAHP/tCBwAABz/5xz/3hz/gBz/vBz/GwgcAHkGHAAPHAAuHAAOHAArHAAEHAAPCBwAIBwAZRwADxwAJhwAHRwAMAgcABocAC0cABYcABkcAA0cAAAIHAAEHAAAHAAFHP/8HAAHHP/3CBwADxz/7xwADRz/+BwAEBwAAAgcACIcABkcAB8cACofHAAoHP/pHAAaHP/eHhz/zRwAABz/1xz/zRz/sRz/YggcAEIcANEFHP/EHP/xHP/tHP/9HP+HHP/2CA4cAYUc/+0WHAFgHAHNFRz/4wYc//Uc/+0c//0c//4c//EcAAAIHP/3HAAAHP/3HAACHP/uHAAHCBz/5xwAChz/8xwAAxz/7BwAAAgc/68c/8sc/84c/7QfHAAAHP/KHAAOHP/jHABCHP+yCBwAJhz/0xwAEhz/3hwAABz/5wgc/+Ic/+cc/+gc/+AeHP/pHAAAHP/rHAALHP/wHAAUCBz/7BwAGRz/+BwAFxz/+BwANggc/+UcAAMFHP/qHP9aBRwAGwYcAAQcAAwcAAwcAAgcAA0cAAAIHAAHHAAAHAALHP/9HAANHP/7CBwAGBz/+BwAExz//BwAFBwAAAgcAFQcAEAcADkcAEsfHAAAHAAtHP/nHAAxHP/IHABBCBz/2hwALRz/7hwAHxwAABwAFggcACAcABUcABUcACAeHAAuHAAAHAAaHP/cHAAQHP+tCBwAGxz//gUOHAEWHP/1FhwBJBwBwRUc/7gGHAAnHACRBRz/3QYc/8oc/68c/84c/9cc/7Ec/+IIHP/dBxwAMwYc/7wc/xIFHP/yHP/NHP/2HP/THAAAHP/vCBz/2RwAHBz/5hwAKh4cADwcAAAcACgcACEcAEIcAGcIHP/qHAAOBRz/3hz/yxz/5hz/5Rz/8BwAAAgc//gc//gcAAgcAAcfHAAAHAASHAASHABJHAAkHAB6CBwABRwAERwACxwAKBwAERwAPQgcAFMGDhwCLBwADxYcAcgcAIUVHP/dHP/LHP/sHP/rHP/vHAAACBz/+Bz/+hwACBwACR8cAAAcAAocAAAcAAAcABkcAF8IHABOHAEMBRz/igYc/8gc/z8c/+Mc/74c/8cc/7gIHP/hHP/aHP/qHP/vHP/sHAAACBz/8hz/+RwACBwAER8cAAAcAA8cAAIcAAgcAA0cACsIHABeHAE0BRz/+hz//xz/+hz//xz/8Bz//Qgc/7Yc//Mc/8Uc//gc/9Ac//4IHP/lBxwALhz//RwAChz/+xwAABz/7AgcAAAc//Ac//sc/+Qc//cc/+MIHP/YHP97BRz/8hz/0Bz/+Rz/3BwAABz/6Agc/88cABoc/+ccADEeHABDHAAAHAAeHAAaHABnHACQCBz/7hz/yRz/+hz/5RwAABz/5Agc/9gcABUc/+wcACweHAA8HAAAHAAxHAAoHAA0HABZCA4cApscABAWHAGXBBwAGRwAABwACRz//xwACBz/+QgcABQc/+4cAA4c/6McAAMc/3YIHAACHP+yBRwAABz//BwAABz//xz//xz/3Qgc//8c/9MFHAAbBhwAORwAVgUcAAYcAAgcACwcAE0cACwcAE8IHAAJHAAQHAACHAAEHAAIHAAPCBwAFxz+4wUcABsGHAC5HADEHABXHACFHAAAHABUCBwAIRz/4xwAHRz/4B4c/+Ic/+Yc/+Qc/+AfHAAAHP/yHAAGHP/yHAAPHP/rCBwADhz/7RwABhz/8xwAABz/9ggcAAAc/+Ic/+Ic/9Ic/6cc/5cIHP/gHAFMBRz/5QYc/6wc/28c//Ac/+Mc/8gc/5sIHP/9HAB9HP/5HABAHP/pHABWCBz/1xz/9xz/4Rz/+xz/rRz/8ggOHAG8HP+iFhwAbBwBmxUcABIcAAAcAAUc//8cAAcc//wIHAARHP/2HAAQHP/XHAAPHP+5CBwAIRz/YBwAERz/nxwAABz/4QgcAAAc/+kc//cc/+gc/+4c/+cIHP/rHP/kHP/lHP/rHP/vHAAACBz/+RwAABz/7RwABxz/+BwABwgc/+8cAAwc/+ccAAkc/+4cAAAIHP/mHP/oHP/mHP/jHxz/3xwAGxz/5RwAIx4cADccAAAcAEEcACgcADccAEIIHACCHACfHAB3HADtHAAAHABlCBwAIhz/4xwAHhz/3x4c/+Ic/+Uc/+Uc/+EfHAAAHP/oHAAHHP/0HAAYHP/uCBwAEhz/8hwABhz/+BwAABz/8wgcAAAc/+Ic/+4c/9Yc/7sc/3sIHP/0HABLBRz/6hwAdhz/5hwAcBz/7hwALwgc/9Ac//Qc/9wc//oc/78c//oIDhwA+hwAfRYOHgoDliX/DAmmCvcMC6aRjpKWlZSdDAyLDA4dAAAAIBMAawEBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2wLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwAAAAAAAwIkAfQABQAAAooCuwAAAIwCigK7AAAB3wAxAQIAAAAABgAAAAAAAACAAACvUAAgSgAAAAAAAAAAKjIxKgABACD7BAPE/rwAZAPEAUQAAAAAAAAAAAHOArAAAAAgAAMAAAABAAMAAQAAAAwABAMcAAAATgBAAAUADgB+AKwA/wExAUIBUwFhAXgBfgGSAscC3QPAIBQgGiAeICIgJiAwIDogRCCsISIhJiICIgYiDyISIhoiHiIrIkgiYCJlJcrgBva++wT//wAAACAAoQCuATEBQQFSAWABeAF9AZICxgLYA8AgEyAYIBwgICAmIDAgOSBEIKwhIiEmIgIiBiIPIhEiGiIeIisiSCJgImQlyuAA9r77AP//AAAAAAAA/s8AAAAAAAD+iAAA/m4AAAAA/EAAAAAAAAAAAN/a39AAAN+831Te3t7a3f7d+t3xAADd5t3i3dXduN2gAADaNgAACUIAAAABAE4BCgEgAAABwAHCAcQAAAHEAAABxAHGAAABzgHQAdQB2AAAAAAB2AAAAAAAAAAAAAAAAAAAAcwAAAAAAAAAAAAAAcQAAAHEAAABzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAMAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAUABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAgACQAKAAsAAAAMAAAADQAOAAAADwAQABEAEgATAAAAFAAVABYAFwAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAXw889QAAA+gAAAAAngt+JwAAAACeC34nAAD+vA//A8QAAgARAAAAAAAAAAAAAQAAA8T+vAAA//8AAAAAAAACsADHAAAAAAAAAAAAAAAAABsAAAAAApsAAALSAAAC0gAAApsAAAIsAAACYwAAAfQAAAH0AAABvAAAAfQAAAG8AAAB9AAAARYAAAEWAAABFgAAAwoAAAIsAAAB9AAAAfQAAAGFAAABhQAAARYAAAIsAAACmwAAAbwAAAD6AAAAAFAAABsAAAAAABQA9gABAAAAAAAAABAAAAABAAAAAAABAB0AEAABAAAAAAACAAcALQABAAAAAAADAAgANAABAAAAAAAEAB0APAABAAAAAAAFAAwAWQABAAAAAAAGAAAAZQABAAAAAAAHAAcAZQABAAAAAAAIAAcAbAABAAAAAAAJAAcAcwADAAEECQAAACAAegADAAEECQABADoAmgADAAEECQACAA4A1AADAAEECQADABAA4gADAAEECQAEADoA8gADAAEECQAFABgBLAADAAEECQAGAAABRAADAAEECQAHAA4BRAADAAEECQAIAA4BUgADAAEECQAJAA4BYE9yaWdpbmFsIGxpY2VuY2VOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbFVua25vd251bmlxdWVJRE5XQkpaTCtOaW1idXNSb21ObzlMLU1lZGlJdGFsVmVyc2lvbiAwLjExVW5rbm93blVua25vd25Vbmtub3duAE8AcgBpAGcAaQBuAGEAbAAgAGwAaQBjAGUAbgBjAGUATgBXAEIASgBaAEwAKwBOAGkAbQBiAHUAcwBSAG8AbQBOAG8AOQBMAC0ATQBlAGQAaQBJAHQAYQBsAFUAbgBrAG4AbwB3AG4AdQBuAGkAcQB1AGUASQBEAE4AVwBCAEoAWgBMACsATgBpAG0AYgB1AHMAUgBvAG0ATgBvADkATAAtAE0AZQBkAGkASQB0AGEAbABWAGUAcgBzAGkAbwBuACAAMAAuADEAMQBVAG4AawBuAG8AdwBuAFUAbgBrAG4AbwB3AG4AVQBuAGsAbgBvAHcAbgADAAD/8LMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA);
+ }
+ .test {
+ /*
+ * Intentionally use a different fallback font than the test file so that
+ * if the font fails to load the test and reference will still be
+ * different.
+ */
+ font-family: test, sans-serif;
+ }
+ </style>
+</head>
+<body>
+ <p class="test">lmnop</p>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_font_mozprintcallback.html b/layout/base/tests/chrome/printpreview_font_mozprintcallback.html
new file mode 100644
index 0000000000..1b4296e90a
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_font_mozprintcallback.html
@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+</head>
+<body>
+ <canvas id="canvas" width="200" height="200"></canvas>
+ <script>
+ const fontData = "data:font/opentype;base64,T1RUTwAJAIAAAwAQQ0ZGICjPfaIAAACcAAAgsU9TLzJlXAb2AAAhUAAAAGBjbWFwiDMtcQAAIbAAAAMoaGVhZKspT7wAACTYAAAANmhoZWEEjgGGAAAlEAAAACRobXR4Mq4AAAAAJTQAAABsbWF4cAAbUAAAACWgAAAABm5hbWUqpGR/AAAlqAAAAmRwb3N0//OzMwAAKAwAAAAgAQAEBAABAQEeTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWwAAQEBRPgbAPgcAfgdAvgeA/gfBB4KAB+Lix4KAB+LiwwH+1z72Pp4+lgFHQAAANkPHQAAAAAQHQAAAQ4RHQAAACAdAAAfthIABQEBDSA9WmBWZXJzaW9uIDAuMTFTZWUgb3JpZ2luYWwgbm90aWNlTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWxOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbE1lZGl1bQAAAAAAJAAlACgALAA0ADUAQgBDAEQARQBGAEgASgBLAE0ATgBPAFAAUQBTAFQAVQBWAFgAWgAAABsCAAEAAwEWAhgDmQUYBpsHQAh9CZoKiQwADO8PEhAOERkR5hPoFVkWCReIGGcZvhqCG9kdEh5mHm6LDhwCmxwAIBYcAoUcAq0VHP/iBhz/8hz/6hz/9xz/+Rz/8RwAAAgc//gcAAAc//EcAAMc//EcAAUIHP/VHAANHP/RHAAIHP/XHAAACBz/IRz/Rxz/Nhz/Dh8c/2UcAG0c/5gcAKMeHABHHAAAHABCHAAWHAA5HAArCBwAHhwAFhwAEBwAEhwAIBwALAgc/+IcABYFHP/OHP/FHP/kHP/pHP/WHP/uCBz/5Bz/9Bz/4Rz/+hz/4RwAAAgc/6Ic/8gcAEEcAG0fHAAAHACSHABIHACtHABbHABMCBwAJRwAHhwAJxwAEBwAKRwAAAgcAFEcADIc/8Qc/54fHAAAHP/zHP//HP/3HP/+HP/zCBwAIBz/+gUOHALSHP/SFhwAjBwChBUcAA4c//4cAA0c//4cAAQc//8IHAAgHP/7HAAKHP/4HAAAHP/oCBwAABz/9hz//Bz/7xz/+Bz/4Agc/4cc/kIFHP/xHP/NHP/wHP/xHP/PHP/6CBz/5wccAR4GHAD/HAC+HACpHADjHxwAqxz/kBwAZhz/Qx4c/t4GHADgHP+7FRwABxwAGxwAEBwACxwAIBwAAAgcAG8cADYc/8Uc/4YfHAAAHP+OHP/eHP+JHP/KHP+zCBz/yxz/tBz/uRz/2xz/oRwAAAgc/9sc/+8cAAscABcfHAAAHAAMHAADHAAQHAAJHAAaCBwAAhwAARwAAhwAAB4OHALSHAAVFhwCrRwBShUc/uEGHP/mBxwAKxz//BwACBz//hwACxz/+ggcAAYc//wcAAYc//UcAAAc//cIHAAAHP/mHP/8HP/vHP/lHP+kCBz/6Rz/shwAABwAABz//Bz/+wgc//Ic/+8c/9cc//Mc/9kcAAAIHP+WHP/GHABCHAB6HxwAABwAlRwARBwApxwAWxwATQgcACYcACAcACscABAcAC0cAAAIHAAvHAAAHAAnHP/tHAAWHP/gCBwAFhz/3xwABxz/4xwAAhz/vAgcAB0c//wFHAAzHADdBRz/4QYc//Uc/+oc//Uc//gc/+wcAAAIHP/3HAAAHP/4HAACHP/tHAAHCBz/0xwADxz/3xwABhz/0hwAAAgc/7QcAAAc/7kc/+4c/8Mc/90IHP98HP+zHP+pHP9pHAAAHP9nCBz/YRwAeRz/khwArx4cAE0cAAAcAGQcABQcADkcABoIHAAcHAANBRwAMBwAtQUcABMcAEYcAAccAAgcADUcAAQIDhwCmxz/6xYcAZ0cAY4VHADYHADGBRwAKxwAJxwACBwAAxwAKxwABggcABkHHP8tBhz/5wccAAkc//8cAAkc//8cAAMcAAAIHAAZHP/9HAAKHP/5HAAAHP/xCBwAABz/4xz/vBz/vBz/Mhz/UAgcAD4cAOQFHAAQHAA0HAATHAAPHAA7HAAFCBwAGQcc/soGHP/nBxwADhz//hwADBz//hwABRz//wgcAB8c//wcAAsc//ccAAAc/+oIHAAAHP/1HP/8HP/rHP/5HP/mCBz/hhz+PwUc//Ac/8sc//Ec//Mc/88c//oIHP/nBxwBIQYcABkHHP/MHAAEHP/yHAAJHAAAHAAhCBwAABwABhwAARwABhwAARwABQgcAEMcAPkFHABxHP8OBRwACRz/7BwABBz/9BwAABz/9QgcAAAc//Ic//Qc//gc/+Yc//4IHP/8HP//HP/1HP//HP/0HP//CBz/5wccARgGHAAZBxz/2hwABBz/8hwABxz/9RwAGAgOHAIsHAACFhwAABz/7hUcAB4GHAAMHAAdHAAHHAAGHAATHAAACBwAChwAABwADRz//RwAFxz/+AgcADEc/+8cACIc//kcACgcAAAIHACMHABZHABPHAB8HxwAABwAVhz/0hwAQxz/hhwAXQgc/8IcAC8c//EcABYcAAAcAC0IHABAHAAoHAApHABAHhwAUBwAABwAJxz/zxwADRz/iwgcABsc//wFHAAoHADJBRz/4gYc//cc/+4c//Ic//gc/+scAAAIHP/3HAAAHP/vHAAEHP/nHAAHCBz/1RwADhz/5RwABRz/5BwAAAgc/44c/6kc/6wc/5IfHAAAHP/lHAAFHP/qHAAIHP/vCBwAFhz/1hwAKRz/1BwAOBz/1AgcAE4c/8QcACMc/9EcAAAc/9IIHAAAHP/qHP/5HP/oHP/0HP/qCBz/6Rz/2Rz/3Rz/7Rz/zxwAAAgc/8wcAAAc/9IcABkc/+ccACoIHP/tHAAfHP/4HAAeHP/8HAA8CBz/4xwAAgUOHAJjHAAyFhwCWBwCnRUc/dkGHP/aHP9VBRwAGRz/+QUcADQcAGQcAD8cACkcAGYcAAIIHP9sHP3nBRz/8hz/zRz/5hz/6xz/zRwAAAgc//IGHP/nBxwBSwYcABkHHP+6HAADHP/zHAAGHAAAHAAgCBwAABwADxwABBwAFRwABxwAGggcAIwcAfoFHABeHP//HAAnHP/VHAAFHP+RCBwAGxz//gUOHAH0HP/rFhwBxxwAfxUc/9cc/8kc//Ec//Ec//AcAAAIHP/5HP/7HAAGHAAJHxwAABwAGRwAChwAKhwAGBwAUQgcAEccAOwFHP+RHP/5BRz/7hz/xQUc//ccADMc/+ocABUc/9UcAAAIHP+FHP9qHP9AHP9kHxz/tBwAKxz/zRwAQR4cAD0cAAAcAC4cACUcADkcAF8IHP/1HP/ZHP/9HP/yHAAAHP/yCBz/2xwAHhz/4xwAJR4cAC8cAAAcAC8cACccADkcAFcIHP9DHAE0FRwAFhz//hwADxz/7hwAABz/5QgcAAAc/8Uc/94c/48c/9oc/7wIHP/lHP/QHP/iHP/lHP/lHAAACBz/5hz/7RwAFxwAHx8cAAAcADQcACEcAGQcACkcAEoIHAAeHAA1HAAhHAAfHAAbHP/+CA4cAfQc//IWHABaHAKCFRwAMRwADhz/+Rz/6R8cAAAc//Mc//Mc/8sc/+Yc/6UIHP+iHP65BRz/9xz/4Bz/9Rz/1BwAABz/+wgc/+QcAEkc/+AcAEEeHACjHACdHACsHACzHxwASRz/0hwAMxz/vh4c/80cAAAc/9wc/+gc/80c/70IHABaHAFIBRz/uhz/8xz/zxz/+Rz/pxz/9ggcAMcc/u0VHAAdHAAPHP/pHP/SHxwAABz/xRz/5Rz/oxz/3Bz/wggc/94c/8Qc/9kc/+Ec/9UcAAAIHP/uHP/zHAAMHAAPHxwAABwACRwAEhwAUhwACBwAIAgcAAscACccABccAD8cAA8cACMIHAAaHAA5HAAgHAAeHAAhHAAACA4cAbwc//sWHAFDHACNFRz/0Bz/vRz/4Rz/6Rz/1RwAAAgc/9Uc/+IcACQcADYfHAAAHAA+HAAaHABaHAAkHABBCBwAGhwALxwAHhwAGBwAHhwAAAgcAAwcAAoc//kc//YfHAAAHP/8HP/+HP/6HP/6HP/3CBz/9xz/8Bz//Bz/9RwAABz/9Agc/+IcABkc/+ocACEeHAAkHAAaHAAdHAApHxwANRz/0hwAJRz/vh4c/3Mc/3Ac/1wc/2AfHP+nHAA/HP/CHABaHhwALBwAABwAKxwAEBwAIhwAHQgcABocABYcABAcABMcACMcADIIDhwB9Bz/6xYcAcAcAIMVHP/lHP/THP/nHP/lHP/xHAAACBz/+xz/+hwABhwABh8cAAAcAAkcABYcAFccACMcAH8IHABvHAGVBRz/xBz/8hz/zxz/+Rz/nBz/+Agc/+UHHAAVBhwAGhwADxz/9hz/7h8cAAAc//cc//oc/+cc/+kc/6wIHP/uHP+8BRz/6BwAGBz/7RwACRz/4xwAAAgc/4Yc/2oc/z8c/2MfHP+4HAAtHP/LHAA9HhwAPxwAABwALhwAJBwAORwAXwgc//gc/9sc//0c//EcAAAc//EIHP/ZHAAWHP/pHAAmHhwAMRwAABwALRwAJhwANxwAWAgc/04cATEVHAAWHP//HAAPHP/rHAAAHP/kCBwAABz/1hz/1Bz/fBz/3hz/wQgc/+Qc/88c/+Mc/+cc/+McAAAIHP/nHAAAHP/uHAAcHAACHAAjCBwAAxwANhwAHxwAXBwAJBwAQQgcACAcADkcACQcACAcAB4c//4IDhwBvBwABRYcATgcAI4VHP/RHP+7HP/kHP/qHP/WHAAACBz/1Rz/6hwAHBwANh8cAAAcABAcAAIcAA0cAAQcABQIHABoHAAUHAAxHAAWHAAyHAAuCBwAIxwAIBwAExwAJhwAABwAIwgcADMc/9QcACQc/8MeHP9wHP9wHP9dHP9bHxz/rBwAQBz/wRwAVx4cAEwcAAAcADYcACccADwcAGMIHP86HABZFRwAJBwAihwALhwATRwAMBwAAAgcABMcAAkc//Qc/+ofHAAAHP+0HP/QHP+/HP+zHP/jCBz/+Rz//hz/8Rz/+xz/9Rz//AgOHAH0HP/MFhwCEhwBrRUc/5EGHP/fHAAYHP/hHAAJHP/SHAAACBz/hRz/nRz/rxz/mx8cAAAc/80cABoc/9wcADYc/+kIHP+vHP/SHP/xHP/yHAAAHP/bCBwAABz/4RwAEhz/7BwALBz/8Qgc/8Ic//Ec/+wc//gc/+kc/+0IHP/vHP/yHP/1HP/oHAAAHP/oCBz/vxwASRz/1xwAcx4cAIwcAGMcAD4cAFgfHAAAHAA8HP/YHAAiHP+THAAfCBz/yxwADwUc/+AcAAkc/+0cAA4cAAAcAA8IHAAQHAAPHAAUHAANHhwABRwAABwABxz//xwACBz//QgcAAsc//0cAAkc//8cAAscAAAIHAAsHAAAHAAsHAAMHAAnHAAWCBwAOhwAIRwAHxwAMxwAABwAPAgcAAAcABAc//8cAAoc//scABAIHABDBhz+lRz+hRUcAAsc//8cAE8c/+YcABQc//YIHAAcHP/zHAANHP/uHAAAHP/mCBz/1Rz/1Bz/5hz/tB4c/78c/9IcACAcAC4fHAAAHAAUHAAJHAAQHAAVHAAUCBwADBwADBwAHxwAEhwABxz//wgcAIwcAbUVHAAaHAATHP/nHP/eHxwAABz/3hz/9Bz/zhz/7xz/3Agc/+oc/9Ic/+cc/+oc/+IcAAAIHP/kHP/xHAAVHAAmHxwAABwAKBwAERwAPRwAFRwAJggcABMcACEcABUcABAcABocAAAIDhwBFhwAAhYcANYcAI0VHP/yHP/sBRz/5hz/2Rz/6Rz/6xz/8RwAAAgc//gc//kcAAccAAgfHAAAHAAGHAAGHAAiHAADHAAMCBwAWxwBTgUc/8oc//Qc/7oc//Yc/7Ic//oIHP/lBxwAKxwAEBz/+Bz/6x8cAAAc//gc//0c//Ec//wc/+8IHP/GHP8pBRz/+Bz/5Bz/+xz/5BwAABz/8wgc/9scABwc/+YcACkeHAA8HAAAHAAlHAAfHABGHABpCBz/1BwCLhUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+AcAB8c/9seDhwBFhz/QxYcANwcAZcVHAAyHAAMHP/7HP/mHxwAABz/9Bz/+xz/5xz/+Rz/4wgc/6Qc/p0FHP/mHP+cHP/vHP/hHP/kHAAACBz/9Rz/8xwABxwABR8cAAAcAAIcAAEcAAIcAAEcAAIIHAALHAAQHAACHAAFHAAAHAALCBwAGxz/6hwAFhz/5R4c/+Yc/+kc/+gc/+MfHP/SHAArHP/hHAA+HhwAZBwAABwARRwATBwAKBwAmwgcAHIcAbYFHP/FHP/zHP/bHP/7HP+QHP/2CBwAsxwA+xUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+EcAB8c/9oeDhwBFhwAAhYcANYcAI0VHP/YHP/FHP/rHP/rHP/vHAAACBz/+Bz/+RwABxwACB8cAAAcAA4cAAgcACIcAA4cADQIHACRHAILBRz/qhz/7xz/0Bz/+hz/tRz/+Qgc/+UHHAALHAABHAAGHAAAHAAEHAAACBwAGxwADxz/9hz/7h8cAAAc//Mc//Ic/8Ic/+wc/70IHP+2HP74BRz/6hz/tBz/9Bz/xhwAABz/6Agc/9wcABsc/+gcACgeHAA+HAAAHAAmHAAfHABFHABpCA4cAwoc//IWHAAwHAGXFRwACAYcACAcAA4c//gc/+4fHAAAHP/yHP/4HP/eHP/lHP+hCBz/vRz/EgUcAHkGHAA1HAC+HAAmHABWHAA4HABDCBwAFhwAGRwAHRwAFRwADRwAAAgcAAocAAkc//Yc//MfHAAAHP/xHP/0HP/WHP/fHP+aCBz/7xz/zBz/8xz/1hz/3Rz/jwgcAHgGHAA7HADAHAAGHAASHAAkHAA/CBwAKhwAShwAKBwAKhwAHhwAAAgcAAwcAAsc//Yc//UfHAAAHP/6HP/9HP/1HP/8HP/zCBz/0xz/fQUc/+oc/8Ec//Ic/8IcAAAc/+QIHP/XHAAYHP/qHAArHhwAPhwAABwAKBwAIRwAOhwAYggc/+ocAA0FHP/7HP/4HP/7HP/5HP/+HP/9CBz/6Rz/3Bz/6xz/6hz/8xwAAAgc//cc//kcAAccAAcfHAAAHAALHAAAHAAAHAAVHABDCBwALRwAhQUcAA8cACscAAgcACYcAAAcABoIHAAoHP/fHAAfHP/UHhz/vBwAABz/0hz/2Bz/rBz/fAgcABMcADIcAAccABocAAAcABwIHAAqHP/nHAAaHP/WHhz/5BwAABz/4hz/9Rz/5Bz/6Qgc/9sc/+Qc/+Ic/9oc/78c/5wIHABAHADHBRz/wBz/8Rz/6xz//Rz/jRz/9wgOHAIsHP/6FhwB3RwAhxUc/9gc/8Ic//Mc//Ic/+8cAAAIHP/4HP/6HAAHHAAKHxwAABwAChwABxwAFxwAEhwANggcACQcAG0FHAAQHAAuHAAKHAAtHAAAHAAYCBwAMBz/5hwAGxz/0R4c/9scAAAc/9wc//Ec/+Qc/+YIHP/cHP/dHP/tHP/oHP+9HP+dCBwAQBwAxgUc/8Ac//Ic/7Ac//Uc/8gc//4IHP/lBxwAKhz//xwADBz/+xwAABz/7QgcAAAc//Uc//Qc/9Ec/94c/4gIHP/lHP+gBRz/9Bz/0hz/+Rz/5xz/9hz/2wgcAHkGHAAvHACtHAAkHABXHAA+HABRCBwAFBwAGxwAHxwAFhwAEhwAAAgcAAwcAA0c//Uc//YfHAAAHP/9HP/+HP/4HP/9HP/2CBz/yRz/WgUc//Ac/9Ac//Qc/8ccAAAc/+YIHP/bHAAaHP/pHAAqHhwAOxwAABwAKhwAIhwAOhwAYQgOHAH0HP/9FhwBHxwBzhUc/20c/3Qc/2Ec/1gfHP+rHABDHP/BHABcHhwAlRwAiBwAmxwAqR8cAFgc/74cAD8c/6UeHP/3HP/jFRwAHRwAEhz/6hz/3B8cAAAc/7wc/+Ec/34c/+Ac/7oIHP/jHP/CHP/iHP/jHP/dHAAACBz/4hz/7RwAGBwAJh8cAAAcAE4cACUcAJIcACMcAD4IHAAaHAAuHAAdHAAXHAAgHAAACA4cAfQc/4gWHACPHAGXFRwALRz//hwACRz//BwAABz/7QgcAAAc//cc//cc/9gc//Mc/88IHP+cHP58BRz/8Bz/wxz/9Bz/8xz/1xwAAAgc//oGHP/lBxwA+QYcABsHHP/PHAABHP/wHAAIHAAAHAAYCBwAABwADBwACBwAIRwAEhwARAgcAAMcAAocAAMcAAscAAIcAAcIHAADHAANBRwAIxz/7hwADBz//BwAFRwAAAgcAIccAI4cALUcAK0fHABKHP/WHAAvHP++Hhz/xxwAABz/1Bz/4Bz/xRz/rQgcACYcAHMFHP+aHP/wHP/ZHP/6HP/MHP/6CBwBAxz/1RUcABoc//4cAA8c/+kc//4c/+AIHP/8HP+/HP/gHP+cHP/cHP+/CBz/4Rz/yhz/3xz/5Bz/3xwAAAgc/+oc/+8cABAcABQfHAAAHAAQHAAIHAAeHAAbHABbCBwAGBwAUhwAChwAHBwAEBwAGAgcABkcACccACEcABkcABoc//4IDhwBhRz/6xYcADAcAZcVHAAtHP/+HAAJHP/8HAAAHP/tCBwAABz/5xz/3hz/gBz/vBz/GwgcAHkGHAAPHAAuHAAOHAArHAAEHAAPCBwAIBwAZRwADxwAJhwAHRwAMAgcABocAC0cABYcABkcAA0cAAAIHAAEHAAAHAAFHP/8HAAHHP/3CBwADxz/7xwADRz/+BwAEBwAAAgcACIcABkcAB8cACofHAAoHP/pHAAaHP/eHhz/zRwAABz/1xz/zRz/sRz/YggcAEIcANEFHP/EHP/xHP/tHP/9HP+HHP/2CA4cAYUc/+0WHAFgHAHNFRz/4wYc//Uc/+0c//0c//4c//EcAAAIHP/3HAAAHP/3HAACHP/uHAAHCBz/5xwAChz/8xwAAxz/7BwAAAgc/68c/8sc/84c/7QfHAAAHP/KHAAOHP/jHABCHP+yCBwAJhz/0xwAEhz/3hwAABz/5wgc/+Ic/+cc/+gc/+AeHP/pHAAAHP/rHAALHP/wHAAUCBz/7BwAGRz/+BwAFxz/+BwANggc/+UcAAMFHP/qHP9aBRwAGwYcAAQcAAwcAAwcAAgcAA0cAAAIHAAHHAAAHAALHP/9HAANHP/7CBwAGBz/+BwAExz//BwAFBwAAAgcAFQcAEAcADkcAEsfHAAAHAAtHP/nHAAxHP/IHABBCBz/2hwALRz/7hwAHxwAABwAFggcACAcABUcABUcACAeHAAuHAAAHAAaHP/cHAAQHP+tCBwAGxz//gUOHAEWHP/1FhwBJBwBwRUc/7gGHAAnHACRBRz/3QYc/8oc/68c/84c/9cc/7Ec/+IIHP/dBxwAMwYc/7wc/xIFHP/yHP/NHP/2HP/THAAAHP/vCBz/2RwAHBz/5hwAKh4cADwcAAAcACgcACEcAEIcAGcIHP/qHAAOBRz/3hz/yxz/5hz/5Rz/8BwAAAgc//gc//gcAAgcAAcfHAAAHAASHAASHABJHAAkHAB6CBwABRwAERwACxwAKBwAERwAPQgcAFMGDhwCLBwADxYcAcgcAIUVHP/dHP/LHP/sHP/rHP/vHAAACBz/+Bz/+hwACBwACR8cAAAcAAocAAAcAAAcABkcAF8IHABOHAEMBRz/igYc/8gc/z8c/+Mc/74c/8cc/7gIHP/hHP/aHP/qHP/vHP/sHAAACBz/8hz/+RwACBwAER8cAAAcAA8cAAIcAAgcAA0cACsIHABeHAE0BRz/+hz//xz/+hz//xz/8Bz//Qgc/7Yc//Mc/8Uc//gc/9Ac//4IHP/lBxwALhz//RwAChz/+xwAABz/7AgcAAAc//Ac//sc/+Qc//cc/+MIHP/YHP97BRz/8hz/0Bz/+Rz/3BwAABz/6Agc/88cABoc/+ccADEeHABDHAAAHAAeHAAaHABnHACQCBz/7hz/yRz/+hz/5RwAABz/5Agc/9gcABUc/+wcACweHAA8HAAAHAAxHAAoHAA0HABZCA4cApscABAWHAGXBBwAGRwAABwACRz//xwACBz/+QgcABQc/+4cAA4c/6McAAMc/3YIHAACHP+yBRwAABz//BwAABz//xz//xz/3Qgc//8c/9MFHAAbBhwAORwAVgUcAAYcAAgcACwcAE0cACwcAE8IHAAJHAAQHAACHAAEHAAIHAAPCBwAFxz+4wUcABsGHAC5HADEHABXHACFHAAAHABUCBwAIRz/4xwAHRz/4B4c/+Ic/+Yc/+Qc/+AfHAAAHP/yHAAGHP/yHAAPHP/rCBwADhz/7RwABhz/8xwAABz/9ggcAAAc/+Ic/+Ic/9Ic/6cc/5cIHP/gHAFMBRz/5QYc/6wc/28c//Ac/+Mc/8gc/5sIHP/9HAB9HP/5HABAHP/pHABWCBz/1xz/9xz/4Rz/+xz/rRz/8ggOHAG8HP+iFhwAbBwBmxUcABIcAAAcAAUc//8cAAcc//wIHAARHP/2HAAQHP/XHAAPHP+5CBwAIRz/YBwAERz/nxwAABz/4QgcAAAc/+kc//cc/+gc/+4c/+cIHP/rHP/kHP/lHP/rHP/vHAAACBz/+RwAABz/7RwABxz/+BwABwgc/+8cAAwc/+ccAAkc/+4cAAAIHP/mHP/oHP/mHP/jHxz/3xwAGxz/5RwAIx4cADccAAAcAEEcACgcADccAEIIHACCHACfHAB3HADtHAAAHABlCBwAIhz/4xwAHhz/3x4c/+Ic/+Uc/+Uc/+EfHAAAHP/oHAAHHP/0HAAYHP/uCBwAEhz/8hwABhz/+BwAABz/8wgcAAAc/+Ic/+4c/9Yc/7sc/3sIHP/0HABLBRz/6hwAdhz/5hwAcBz/7hwALwgc/9Ac//Qc/9wc//oc/78c//oIDhwA+hwAfRYOHgoDliX/DAmmCvcMC6aRjpKWlZSdDAyLDA4dAAAAIBMAawEBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2wLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwAAAAAAAwIkAfQABQAAAooCuwAAAIwCigK7AAAB3wAxAQIAAAAABgAAAAAAAACAAACvUAAgSgAAAAAAAAAAKjIxKgABACD7BAPE/rwAZAPEAUQAAAAAAAAAAAHOArAAAAAgAAMAAAABAAMAAQAAAAwABAMcAAAATgBAAAUADgB+AKwA/wExAUIBUwFhAXgBfgGSAscC3QPAIBQgGiAeICIgJiAwIDogRCCsISIhJiICIgYiDyISIhoiHiIrIkgiYCJlJcrgBva++wT//wAAACAAoQCuATEBQQFSAWABeAF9AZICxgLYA8AgEyAYIBwgICAmIDAgOSBEIKwhIiEmIgIiBiIPIhEiGiIeIisiSCJgImQlyuAA9r77AP//AAAAAAAA/s8AAAAAAAD+iAAA/m4AAAAA/EAAAAAAAAAAAN/a39AAAN+831Te3t7a3f7d+t3xAADd5t3i3dXduN2gAADaNgAACUIAAAABAE4BCgEgAAABwAHCAcQAAAHEAAABxAHGAAABzgHQAdQB2AAAAAAB2AAAAAAAAAAAAAAAAAAAAcwAAAAAAAAAAAAAAcQAAAHEAAABzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAMAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAUABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAgACQAKAAsAAAAMAAAADQAOAAAADwAQABEAEgATAAAAFAAVABYAFwAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAXw889QAAA+gAAAAAngt+JwAAAACeC34nAAD+vA//A8QAAgARAAAAAAAAAAAAAQAAA8T+vAAA//8AAAAAAAACsADHAAAAAAAAAAAAAAAAABsAAAAAApsAAALSAAAC0gAAApsAAAIsAAACYwAAAfQAAAH0AAABvAAAAfQAAAG8AAAB9AAAARYAAAEWAAABFgAAAwoAAAIsAAAB9AAAAfQAAAGFAAABhQAAARYAAAIsAAACmwAAAbwAAAD6AAAAAFAAABsAAAAAABQA9gABAAAAAAAAABAAAAABAAAAAAABAB0AEAABAAAAAAACAAcALQABAAAAAAADAAgANAABAAAAAAAEAB0APAABAAAAAAAFAAwAWQABAAAAAAAGAAAAZQABAAAAAAAHAAcAZQABAAAAAAAIAAcAbAABAAAAAAAJAAcAcwADAAEECQAAACAAegADAAEECQABADoAmgADAAEECQACAA4A1AADAAEECQADABAA4gADAAEECQAEADoA8gADAAEECQAFABgBLAADAAEECQAGAAABRAADAAEECQAHAA4BRAADAAEECQAIAA4BUgADAAEECQAJAA4BYE9yaWdpbmFsIGxpY2VuY2VOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbFVua25vd251bmlxdWVJRE5XQkpaTCtOaW1idXNSb21ObzlMLU1lZGlJdGFsVmVyc2lvbiAwLjExVW5rbm93blVua25vd25Vbmtub3duAE8AcgBpAGcAaQBuAGEAbAAgAGwAaQBjAGUAbgBjAGUATgBXAEIASgBaAEwAKwBOAGkAbQBiAHUAcwBSAG8AbQBOAG8AOQBMAC0ATQBlAGQAaQBJAHQAYQBsAFUAbgBrAG4AbwB3AG4AdQBuAGkAcQB1AGUASQBEAE4AVwBCAEoAWgBMACsATgBpAG0AYgB1AHMAUgBvAG0ATgBvADkATAAtAE0AZQBkAGkASQB0AGEAbABWAGUAcgBzAGkAbwBuACAAMAAuADEAMQBVAG4AawBuAG8AdwBuAFUAbgBrAG4AbwB3AG4AVQBuAGsAbgBvAHcAbgADAAD/8LMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
+ let canvas = document.getElementById('canvas');
+ canvas.mozPrintCallback = (obj) => {
+ let testFontFace = new FontFace('test', 'url(' + fontData + ')');
+ document.fonts.add(testFontFace);
+ testFontFace.load().then(() => {
+ let ctx = obj.context;
+ ctx.font = '10px test, monospace';
+ ctx.fillText("lmnop", 20, 20);
+ obj.done();
+ window.postMessage("ready", "*");
+ }).catch((e) => {
+ obj.done();
+ window.postMessage("error", "*");
+ });
+ };
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_font_mozprintcallback_ref.html b/layout/base/tests/chrome/printpreview_font_mozprintcallback_ref.html
new file mode 100644
index 0000000000..e4dd82bc58
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_font_mozprintcallback_ref.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8">
+ <style type="text/css">
+ @font-face {
+ font-family: test;
+ src: url(data:font/opentype;base64,T1RUTwAJAIAAAwAQQ0ZGICjPfaIAAACcAAAgsU9TLzJlXAb2AAAhUAAAAGBjbWFwiDMtcQAAIbAAAAMoaGVhZKspT7wAACTYAAAANmhoZWEEjgGGAAAlEAAAACRobXR4Mq4AAAAAJTQAAABsbWF4cAAbUAAAACWgAAAABm5hbWUqpGR/AAAlqAAAAmRwb3N0//OzMwAAKAwAAAAgAQAEBAABAQEeTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWwAAQEBRPgbAPgcAfgdAvgeA/gfBB4KAB+Lix4KAB+LiwwH+1z72Pp4+lgFHQAAANkPHQAAAAAQHQAAAQ4RHQAAACAdAAAfthIABQEBDSA9WmBWZXJzaW9uIDAuMTFTZWUgb3JpZ2luYWwgbm90aWNlTldCSlpMK05pbWJ1c1JvbU5vOUwtTWVkaUl0YWxOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbE1lZGl1bQAAAAAAJAAlACgALAA0ADUAQgBDAEQARQBGAEgASgBLAE0ATgBPAFAAUQBTAFQAVQBWAFgAWgAAABsCAAEAAwEWAhgDmQUYBpsHQAh9CZoKiQwADO8PEhAOERkR5hPoFVkWCReIGGcZvhqCG9kdEh5mHm6LDhwCmxwAIBYcAoUcAq0VHP/iBhz/8hz/6hz/9xz/+Rz/8RwAAAgc//gcAAAc//EcAAMc//EcAAUIHP/VHAANHP/RHAAIHP/XHAAACBz/IRz/Rxz/Nhz/Dh8c/2UcAG0c/5gcAKMeHABHHAAAHABCHAAWHAA5HAArCBwAHhwAFhwAEBwAEhwAIBwALAgc/+IcABYFHP/OHP/FHP/kHP/pHP/WHP/uCBz/5Bz/9Bz/4Rz/+hz/4RwAAAgc/6Ic/8gcAEEcAG0fHAAAHACSHABIHACtHABbHABMCBwAJRwAHhwAJxwAEBwAKRwAAAgcAFEcADIc/8Qc/54fHAAAHP/zHP//HP/3HP/+HP/zCBwAIBz/+gUOHALSHP/SFhwAjBwChBUcAA4c//4cAA0c//4cAAQc//8IHAAgHP/7HAAKHP/4HAAAHP/oCBwAABz/9hz//Bz/7xz/+Bz/4Agc/4cc/kIFHP/xHP/NHP/wHP/xHP/PHP/6CBz/5wccAR4GHAD/HAC+HACpHADjHxwAqxz/kBwAZhz/Qx4c/t4GHADgHP+7FRwABxwAGxwAEBwACxwAIBwAAAgcAG8cADYc/8Uc/4YfHAAAHP+OHP/eHP+JHP/KHP+zCBz/yxz/tBz/uRz/2xz/oRwAAAgc/9sc/+8cAAscABcfHAAAHAAMHAADHAAQHAAJHAAaCBwAAhwAARwAAhwAAB4OHALSHAAVFhwCrRwBShUc/uEGHP/mBxwAKxz//BwACBz//hwACxz/+ggcAAYc//wcAAYc//UcAAAc//cIHAAAHP/mHP/8HP/vHP/lHP+kCBz/6Rz/shwAABwAABz//Bz/+wgc//Ic/+8c/9cc//Mc/9kcAAAIHP+WHP/GHABCHAB6HxwAABwAlRwARBwApxwAWxwATQgcACYcACAcACscABAcAC0cAAAIHAAvHAAAHAAnHP/tHAAWHP/gCBwAFhz/3xwABxz/4xwAAhz/vAgcAB0c//wFHAAzHADdBRz/4QYc//Uc/+oc//Uc//gc/+wcAAAIHP/3HAAAHP/4HAACHP/tHAAHCBz/0xwADxz/3xwABhz/0hwAAAgc/7QcAAAc/7kc/+4c/8Mc/90IHP98HP+zHP+pHP9pHAAAHP9nCBz/YRwAeRz/khwArx4cAE0cAAAcAGQcABQcADkcABoIHAAcHAANBRwAMBwAtQUcABMcAEYcAAccAAgcADUcAAQIDhwCmxz/6xYcAZ0cAY4VHADYHADGBRwAKxwAJxwACBwAAxwAKxwABggcABkHHP8tBhz/5wccAAkc//8cAAkc//8cAAMcAAAIHAAZHP/9HAAKHP/5HAAAHP/xCBwAABz/4xz/vBz/vBz/Mhz/UAgcAD4cAOQFHAAQHAA0HAATHAAPHAA7HAAFCBwAGQcc/soGHP/nBxwADhz//hwADBz//hwABRz//wgcAB8c//wcAAsc//ccAAAc/+oIHAAAHP/1HP/8HP/rHP/5HP/mCBz/hhz+PwUc//Ac/8sc//Ec//Mc/88c//oIHP/nBxwBIQYcABkHHP/MHAAEHP/yHAAJHAAAHAAhCBwAABwABhwAARwABhwAARwABQgcAEMcAPkFHABxHP8OBRwACRz/7BwABBz/9BwAABz/9QgcAAAc//Ic//Qc//gc/+Yc//4IHP/8HP//HP/1HP//HP/0HP//CBz/5wccARgGHAAZBxz/2hwABBz/8hwABxz/9RwAGAgOHAIsHAACFhwAABz/7hUcAB4GHAAMHAAdHAAHHAAGHAATHAAACBwAChwAABwADRz//RwAFxz/+AgcADEc/+8cACIc//kcACgcAAAIHACMHABZHABPHAB8HxwAABwAVhz/0hwAQxz/hhwAXQgc/8IcAC8c//EcABYcAAAcAC0IHABAHAAoHAApHABAHhwAUBwAABwAJxz/zxwADRz/iwgcABsc//wFHAAoHADJBRz/4gYc//cc/+4c//Ic//gc/+scAAAIHP/3HAAAHP/vHAAEHP/nHAAHCBz/1RwADhz/5RwABRz/5BwAAAgc/44c/6kc/6wc/5IfHAAAHP/lHAAFHP/qHAAIHP/vCBwAFhz/1hwAKRz/1BwAOBz/1AgcAE4c/8QcACMc/9EcAAAc/9IIHAAAHP/qHP/5HP/oHP/0HP/qCBz/6Rz/2Rz/3Rz/7Rz/zxwAAAgc/8wcAAAc/9IcABkc/+ccACoIHP/tHAAfHP/4HAAeHP/8HAA8CBz/4xwAAgUOHAJjHAAyFhwCWBwCnRUc/dkGHP/aHP9VBRwAGRz/+QUcADQcAGQcAD8cACkcAGYcAAIIHP9sHP3nBRz/8hz/zRz/5hz/6xz/zRwAAAgc//IGHP/nBxwBSwYcABkHHP+6HAADHP/zHAAGHAAAHAAgCBwAABwADxwABBwAFRwABxwAGggcAIwcAfoFHABeHP//HAAnHP/VHAAFHP+RCBwAGxz//gUOHAH0HP/rFhwBxxwAfxUc/9cc/8kc//Ec//Ec//AcAAAIHP/5HP/7HAAGHAAJHxwAABwAGRwAChwAKhwAGBwAUQgcAEccAOwFHP+RHP/5BRz/7hz/xQUc//ccADMc/+ocABUc/9UcAAAIHP+FHP9qHP9AHP9kHxz/tBwAKxz/zRwAQR4cAD0cAAAcAC4cACUcADkcAF8IHP/1HP/ZHP/9HP/yHAAAHP/yCBz/2xwAHhz/4xwAJR4cAC8cAAAcAC8cACccADkcAFcIHP9DHAE0FRwAFhz//hwADxz/7hwAABz/5QgcAAAc/8Uc/94c/48c/9oc/7wIHP/lHP/QHP/iHP/lHP/lHAAACBz/5hz/7RwAFxwAHx8cAAAcADQcACEcAGQcACkcAEoIHAAeHAA1HAAhHAAfHAAbHP/+CA4cAfQc//IWHABaHAKCFRwAMRwADhz/+Rz/6R8cAAAc//Mc//Mc/8sc/+Yc/6UIHP+iHP65BRz/9xz/4Bz/9Rz/1BwAABz/+wgc/+QcAEkc/+AcAEEeHACjHACdHACsHACzHxwASRz/0hwAMxz/vh4c/80cAAAc/9wc/+gc/80c/70IHABaHAFIBRz/uhz/8xz/zxz/+Rz/pxz/9ggcAMcc/u0VHAAdHAAPHP/pHP/SHxwAABz/xRz/5Rz/oxz/3Bz/wggc/94c/8Qc/9kc/+Ec/9UcAAAIHP/uHP/zHAAMHAAPHxwAABwACRwAEhwAUhwACBwAIAgcAAscACccABccAD8cAA8cACMIHAAaHAA5HAAgHAAeHAAhHAAACA4cAbwc//sWHAFDHACNFRz/0Bz/vRz/4Rz/6Rz/1RwAAAgc/9Uc/+IcACQcADYfHAAAHAA+HAAaHABaHAAkHABBCBwAGhwALxwAHhwAGBwAHhwAAAgcAAwcAAoc//kc//YfHAAAHP/8HP/+HP/6HP/6HP/3CBz/9xz/8Bz//Bz/9RwAABz/9Agc/+IcABkc/+ocACEeHAAkHAAaHAAdHAApHxwANRz/0hwAJRz/vh4c/3Mc/3Ac/1wc/2AfHP+nHAA/HP/CHABaHhwALBwAABwAKxwAEBwAIhwAHQgcABocABYcABAcABMcACMcADIIDhwB9Bz/6xYcAcAcAIMVHP/lHP/THP/nHP/lHP/xHAAACBz/+xz/+hwABhwABh8cAAAcAAkcABYcAFccACMcAH8IHABvHAGVBRz/xBz/8hz/zxz/+Rz/nBz/+Agc/+UHHAAVBhwAGhwADxz/9hz/7h8cAAAc//cc//oc/+cc/+kc/6wIHP/uHP+8BRz/6BwAGBz/7RwACRz/4xwAAAgc/4Yc/2oc/z8c/2MfHP+4HAAtHP/LHAA9HhwAPxwAABwALhwAJBwAORwAXwgc//gc/9sc//0c//EcAAAc//EIHP/ZHAAWHP/pHAAmHhwAMRwAABwALRwAJhwANxwAWAgc/04cATEVHAAWHP//HAAPHP/rHAAAHP/kCBwAABz/1hz/1Bz/fBz/3hz/wQgc/+Qc/88c/+Mc/+cc/+McAAAIHP/nHAAAHP/uHAAcHAACHAAjCBwAAxwANhwAHxwAXBwAJBwAQQgcACAcADkcACQcACAcAB4c//4IDhwBvBwABRYcATgcAI4VHP/RHP+7HP/kHP/qHP/WHAAACBz/1Rz/6hwAHBwANh8cAAAcABAcAAIcAA0cAAQcABQIHABoHAAUHAAxHAAWHAAyHAAuCBwAIxwAIBwAExwAJhwAABwAIwgcADMc/9QcACQc/8MeHP9wHP9wHP9dHP9bHxz/rBwAQBz/wRwAVx4cAEwcAAAcADYcACccADwcAGMIHP86HABZFRwAJBwAihwALhwATRwAMBwAAAgcABMcAAkc//Qc/+ofHAAAHP+0HP/QHP+/HP+zHP/jCBz/+Rz//hz/8Rz/+xz/9Rz//AgOHAH0HP/MFhwCEhwBrRUc/5EGHP/fHAAYHP/hHAAJHP/SHAAACBz/hRz/nRz/rxz/mx8cAAAc/80cABoc/9wcADYc/+kIHP+vHP/SHP/xHP/yHAAAHP/bCBwAABz/4RwAEhz/7BwALBz/8Qgc/8Ic//Ec/+wc//gc/+kc/+0IHP/vHP/yHP/1HP/oHAAAHP/oCBz/vxwASRz/1xwAcx4cAIwcAGMcAD4cAFgfHAAAHAA8HP/YHAAiHP+THAAfCBz/yxwADwUc/+AcAAkc/+0cAA4cAAAcAA8IHAAQHAAPHAAUHAANHhwABRwAABwABxz//xwACBz//QgcAAsc//0cAAkc//8cAAscAAAIHAAsHAAAHAAsHAAMHAAnHAAWCBwAOhwAIRwAHxwAMxwAABwAPAgcAAAcABAc//8cAAoc//scABAIHABDBhz+lRz+hRUcAAsc//8cAE8c/+YcABQc//YIHAAcHP/zHAANHP/uHAAAHP/mCBz/1Rz/1Bz/5hz/tB4c/78c/9IcACAcAC4fHAAAHAAUHAAJHAAQHAAVHAAUCBwADBwADBwAHxwAEhwABxz//wgcAIwcAbUVHAAaHAATHP/nHP/eHxwAABz/3hz/9Bz/zhz/7xz/3Agc/+oc/9Ic/+cc/+oc/+IcAAAIHP/kHP/xHAAVHAAmHxwAABwAKBwAERwAPRwAFRwAJggcABMcACEcABUcABAcABocAAAIDhwBFhwAAhYcANYcAI0VHP/yHP/sBRz/5hz/2Rz/6Rz/6xz/8RwAAAgc//gc//kcAAccAAgfHAAAHAAGHAAGHAAiHAADHAAMCBwAWxwBTgUc/8oc//Qc/7oc//Yc/7Ic//oIHP/lBxwAKxwAEBz/+Bz/6x8cAAAc//gc//0c//Ec//wc/+8IHP/GHP8pBRz/+Bz/5Bz/+xz/5BwAABz/8wgc/9scABwc/+YcACkeHAA8HAAAHAAlHAAfHABGHABpCBz/1BwCLhUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+AcAB8c/9seDhwBFhz/QxYcANwcAZcVHAAyHAAMHP/7HP/mHxwAABz/9Bz/+xz/5xz/+Rz/4wgc/6Qc/p0FHP/mHP+cHP/vHP/hHP/kHAAACBz/9Rz/8xwABxwABR8cAAAcAAIcAAEcAAIcAAEcAAIIHAALHAAQHAACHAAFHAAAHAALCBwAGxz/6hwAFhz/5R4c/+Yc/+kc/+gc/+MfHP/SHAArHP/hHAA+HhwAZBwAABwARRwATBwAKBwAmwgcAHIcAbYFHP/FHP/zHP/bHP/7HP+QHP/2CBwAsxwA+xUc/90c/+Ec/+Ec/90fHP/ZHAAdHP/iHAAlHhwAJhwAHxwAHhwAJR8cACUc/+EcAB8c/9oeDhwBFhwAAhYcANYcAI0VHP/YHP/FHP/rHP/rHP/vHAAACBz/+Bz/+RwABxwACB8cAAAcAA4cAAgcACIcAA4cADQIHACRHAILBRz/qhz/7xz/0Bz/+hz/tRz/+Qgc/+UHHAALHAABHAAGHAAAHAAEHAAACBwAGxwADxz/9hz/7h8cAAAc//Mc//Ic/8Ic/+wc/70IHP+2HP74BRz/6hz/tBz/9Bz/xhwAABz/6Agc/9wcABsc/+gcACgeHAA+HAAAHAAmHAAfHABFHABpCA4cAwoc//IWHAAwHAGXFRwACAYcACAcAA4c//gc/+4fHAAAHP/yHP/4HP/eHP/lHP+hCBz/vRz/EgUcAHkGHAA1HAC+HAAmHABWHAA4HABDCBwAFhwAGRwAHRwAFRwADRwAAAgcAAocAAkc//Yc//MfHAAAHP/xHP/0HP/WHP/fHP+aCBz/7xz/zBz/8xz/1hz/3Rz/jwgcAHgGHAA7HADAHAAGHAASHAAkHAA/CBwAKhwAShwAKBwAKhwAHhwAAAgcAAwcAAsc//Yc//UfHAAAHP/6HP/9HP/1HP/8HP/zCBz/0xz/fQUc/+oc/8Ec//Ic/8IcAAAc/+QIHP/XHAAYHP/qHAArHhwAPhwAABwAKBwAIRwAOhwAYggc/+ocAA0FHP/7HP/4HP/7HP/5HP/+HP/9CBz/6Rz/3Bz/6xz/6hz/8xwAAAgc//cc//kcAAccAAcfHAAAHAALHAAAHAAAHAAVHABDCBwALRwAhQUcAA8cACscAAgcACYcAAAcABoIHAAoHP/fHAAfHP/UHhz/vBwAABz/0hz/2Bz/rBz/fAgcABMcADIcAAccABocAAAcABwIHAAqHP/nHAAaHP/WHhz/5BwAABz/4hz/9Rz/5Bz/6Qgc/9sc/+Qc/+Ic/9oc/78c/5wIHABAHADHBRz/wBz/8Rz/6xz//Rz/jRz/9wgOHAIsHP/6FhwB3RwAhxUc/9gc/8Ic//Mc//Ic/+8cAAAIHP/4HP/6HAAHHAAKHxwAABwAChwABxwAFxwAEhwANggcACQcAG0FHAAQHAAuHAAKHAAtHAAAHAAYCBwAMBz/5hwAGxz/0R4c/9scAAAc/9wc//Ec/+Qc/+YIHP/cHP/dHP/tHP/oHP+9HP+dCBwAQBwAxgUc/8Ac//Ic/7Ac//Uc/8gc//4IHP/lBxwAKhz//xwADBz/+xwAABz/7QgcAAAc//Uc//Qc/9Ec/94c/4gIHP/lHP+gBRz/9Bz/0hz/+Rz/5xz/9hz/2wgcAHkGHAAvHACtHAAkHABXHAA+HABRCBwAFBwAGxwAHxwAFhwAEhwAAAgcAAwcAA0c//Uc//YfHAAAHP/9HP/+HP/4HP/9HP/2CBz/yRz/WgUc//Ac/9Ac//Qc/8ccAAAc/+YIHP/bHAAaHP/pHAAqHhwAOxwAABwAKhwAIhwAOhwAYQgOHAH0HP/9FhwBHxwBzhUc/20c/3Qc/2Ec/1gfHP+rHABDHP/BHABcHhwAlRwAiBwAmxwAqR8cAFgc/74cAD8c/6UeHP/3HP/jFRwAHRwAEhz/6hz/3B8cAAAc/7wc/+Ec/34c/+Ac/7oIHP/jHP/CHP/iHP/jHP/dHAAACBz/4hz/7RwAGBwAJh8cAAAcAE4cACUcAJIcACMcAD4IHAAaHAAuHAAdHAAXHAAgHAAACA4cAfQc/4gWHACPHAGXFRwALRz//hwACRz//BwAABz/7QgcAAAc//cc//cc/9gc//Mc/88IHP+cHP58BRz/8Bz/wxz/9Bz/8xz/1xwAAAgc//oGHP/lBxwA+QYcABsHHP/PHAABHP/wHAAIHAAAHAAYCBwAABwADBwACBwAIRwAEhwARAgcAAMcAAocAAMcAAscAAIcAAcIHAADHAANBRwAIxz/7hwADBz//BwAFRwAAAgcAIccAI4cALUcAK0fHABKHP/WHAAvHP++Hhz/xxwAABz/1Bz/4Bz/xRz/rQgcACYcAHMFHP+aHP/wHP/ZHP/6HP/MHP/6CBwBAxz/1RUcABoc//4cAA8c/+kc//4c/+AIHP/8HP+/HP/gHP+cHP/cHP+/CBz/4Rz/yhz/3xz/5Bz/3xwAAAgc/+oc/+8cABAcABQfHAAAHAAQHAAIHAAeHAAbHABbCBwAGBwAUhwAChwAHBwAEBwAGAgcABkcACccACEcABkcABoc//4IDhwBhRz/6xYcADAcAZcVHAAtHP/+HAAJHP/8HAAAHP/tCBwAABz/5xz/3hz/gBz/vBz/GwgcAHkGHAAPHAAuHAAOHAArHAAEHAAPCBwAIBwAZRwADxwAJhwAHRwAMAgcABocAC0cABYcABkcAA0cAAAIHAAEHAAAHAAFHP/8HAAHHP/3CBwADxz/7xwADRz/+BwAEBwAAAgcACIcABkcAB8cACofHAAoHP/pHAAaHP/eHhz/zRwAABz/1xz/zRz/sRz/YggcAEIcANEFHP/EHP/xHP/tHP/9HP+HHP/2CA4cAYUc/+0WHAFgHAHNFRz/4wYc//Uc/+0c//0c//4c//EcAAAIHP/3HAAAHP/3HAACHP/uHAAHCBz/5xwAChz/8xwAAxz/7BwAAAgc/68c/8sc/84c/7QfHAAAHP/KHAAOHP/jHABCHP+yCBwAJhz/0xwAEhz/3hwAABz/5wgc/+Ic/+cc/+gc/+AeHP/pHAAAHP/rHAALHP/wHAAUCBz/7BwAGRz/+BwAFxz/+BwANggc/+UcAAMFHP/qHP9aBRwAGwYcAAQcAAwcAAwcAAgcAA0cAAAIHAAHHAAAHAALHP/9HAANHP/7CBwAGBz/+BwAExz//BwAFBwAAAgcAFQcAEAcADkcAEsfHAAAHAAtHP/nHAAxHP/IHABBCBz/2hwALRz/7hwAHxwAABwAFggcACAcABUcABUcACAeHAAuHAAAHAAaHP/cHAAQHP+tCBwAGxz//gUOHAEWHP/1FhwBJBwBwRUc/7gGHAAnHACRBRz/3QYc/8oc/68c/84c/9cc/7Ec/+IIHP/dBxwAMwYc/7wc/xIFHP/yHP/NHP/2HP/THAAAHP/vCBz/2RwAHBz/5hwAKh4cADwcAAAcACgcACEcAEIcAGcIHP/qHAAOBRz/3hz/yxz/5hz/5Rz/8BwAAAgc//gc//gcAAgcAAcfHAAAHAASHAASHABJHAAkHAB6CBwABRwAERwACxwAKBwAERwAPQgcAFMGDhwCLBwADxYcAcgcAIUVHP/dHP/LHP/sHP/rHP/vHAAACBz/+Bz/+hwACBwACR8cAAAcAAocAAAcAAAcABkcAF8IHABOHAEMBRz/igYc/8gc/z8c/+Mc/74c/8cc/7gIHP/hHP/aHP/qHP/vHP/sHAAACBz/8hz/+RwACBwAER8cAAAcAA8cAAIcAAgcAA0cACsIHABeHAE0BRz/+hz//xz/+hz//xz/8Bz//Qgc/7Yc//Mc/8Uc//gc/9Ac//4IHP/lBxwALhz//RwAChz/+xwAABz/7AgcAAAc//Ac//sc/+Qc//cc/+MIHP/YHP97BRz/8hz/0Bz/+Rz/3BwAABz/6Agc/88cABoc/+ccADEeHABDHAAAHAAeHAAaHABnHACQCBz/7hz/yRz/+hz/5RwAABz/5Agc/9gcABUc/+wcACweHAA8HAAAHAAxHAAoHAA0HABZCA4cApscABAWHAGXBBwAGRwAABwACRz//xwACBz/+QgcABQc/+4cAA4c/6McAAMc/3YIHAACHP+yBRwAABz//BwAABz//xz//xz/3Qgc//8c/9MFHAAbBhwAORwAVgUcAAYcAAgcACwcAE0cACwcAE8IHAAJHAAQHAACHAAEHAAIHAAPCBwAFxz+4wUcABsGHAC5HADEHABXHACFHAAAHABUCBwAIRz/4xwAHRz/4B4c/+Ic/+Yc/+Qc/+AfHAAAHP/yHAAGHP/yHAAPHP/rCBwADhz/7RwABhz/8xwAABz/9ggcAAAc/+Ic/+Ic/9Ic/6cc/5cIHP/gHAFMBRz/5QYc/6wc/28c//Ac/+Mc/8gc/5sIHP/9HAB9HP/5HABAHP/pHABWCBz/1xz/9xz/4Rz/+xz/rRz/8ggOHAG8HP+iFhwAbBwBmxUcABIcAAAcAAUc//8cAAcc//wIHAARHP/2HAAQHP/XHAAPHP+5CBwAIRz/YBwAERz/nxwAABz/4QgcAAAc/+kc//cc/+gc/+4c/+cIHP/rHP/kHP/lHP/rHP/vHAAACBz/+RwAABz/7RwABxz/+BwABwgc/+8cAAwc/+ccAAkc/+4cAAAIHP/mHP/oHP/mHP/jHxz/3xwAGxz/5RwAIx4cADccAAAcAEEcACgcADccAEIIHACCHACfHAB3HADtHAAAHABlCBwAIhz/4xwAHhz/3x4c/+Ic/+Uc/+Uc/+EfHAAAHP/oHAAHHP/0HAAYHP/uCBwAEhz/8hwABhz/+BwAABz/8wgcAAAc/+Ic/+4c/9Yc/7sc/3sIHP/0HABLBRz/6hwAdhz/5hwAcBz/7hwALwgc/9Ac//Qc/9wc//oc/78c//oIDhwA+hwAfRYOHgoDliX/DAmmCvcMC6aRjpKWlZSdDAyLDA4dAAAAIBMAawEBAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIyQlJicoKSorLC0uLzAxMjM0NTY3ODk6Ozw9Pj9AQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpbXF1eX2BhYmNkZWZnaGlqa2wLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwsLCwAAAAAAAwIkAfQABQAAAooCuwAAAIwCigK7AAAB3wAxAQIAAAAABgAAAAAAAACAAACvUAAgSgAAAAAAAAAAKjIxKgABACD7BAPE/rwAZAPEAUQAAAAAAAAAAAHOArAAAAAgAAMAAAABAAMAAQAAAAwABAMcAAAATgBAAAUADgB+AKwA/wExAUIBUwFhAXgBfgGSAscC3QPAIBQgGiAeICIgJiAwIDogRCCsISIhJiICIgYiDyISIhoiHiIrIkgiYCJlJcrgBva++wT//wAAACAAoQCuATEBQQFSAWABeAF9AZICxgLYA8AgEyAYIBwgICAmIDAgOSBEIKwhIiEmIgIiBiIPIhEiGiIeIisiSCJgImQlyuAA9r77AP//AAAAAAAA/s8AAAAAAAD+iAAA/m4AAAAA/EAAAAAAAAAAAN/a39AAAN+831Te3t7a3f7d+t3xAADd5t3i3dXduN2gAADaNgAACUIAAAABAE4BCgEgAAABwAHCAcQAAAHEAAABxAHGAAABzgHQAdQB2AAAAAAB2AAAAAAAAAAAAAAAAAAAAcwAAAAAAAAAAAAAAcQAAAHEAAABzgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAIAAAAAAAMAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAUABgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHAAgACQAKAAsAAAAMAAAADQAOAAAADwAQABEAEgATAAAAFAAVABYAFwAAABgAAAAZAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAAAEAAAAAAAXw889QAAA+gAAAAAngt+JwAAAACeC34nAAD+vA//A8QAAgARAAAAAAAAAAAAAQAAA8T+vAAA//8AAAAAAAACsADHAAAAAAAAAAAAAAAAABsAAAAAApsAAALSAAAC0gAAApsAAAIsAAACYwAAAfQAAAH0AAABvAAAAfQAAAG8AAAB9AAAARYAAAEWAAABFgAAAwoAAAIsAAAB9AAAAfQAAAGFAAABhQAAARYAAAIsAAACmwAAAbwAAAD6AAAAAFAAABsAAAAAABQA9gABAAAAAAAAABAAAAABAAAAAAABAB0AEAABAAAAAAACAAcALQABAAAAAAADAAgANAABAAAAAAAEAB0APAABAAAAAAAFAAwAWQABAAAAAAAGAAAAZQABAAAAAAAHAAcAZQABAAAAAAAIAAcAbAABAAAAAAAJAAcAcwADAAEECQAAACAAegADAAEECQABADoAmgADAAEECQACAA4A1AADAAEECQADABAA4gADAAEECQAEADoA8gADAAEECQAFABgBLAADAAEECQAGAAABRAADAAEECQAHAA4BRAADAAEECQAIAA4BUgADAAEECQAJAA4BYE9yaWdpbmFsIGxpY2VuY2VOV0JKWkwrTmltYnVzUm9tTm85TC1NZWRpSXRhbFVua25vd251bmlxdWVJRE5XQkpaTCtOaW1idXNSb21ObzlMLU1lZGlJdGFsVmVyc2lvbiAwLjExVW5rbm93blVua25vd25Vbmtub3duAE8AcgBpAGcAaQBuAGEAbAAgAGwAaQBjAGUAbgBjAGUATgBXAEIASgBaAEwAKwBOAGkAbQBiAHUAcwBSAG8AbQBOAG8AOQBMAC0ATQBlAGQAaQBJAHQAYQBsAFUAbgBrAG4AbwB3AG4AdQBuAGkAcQB1AGUASQBEAE4AVwBCAEoAWgBMACsATgBpAG0AYgB1AHMAUgBvAG0ATgBvADkATAAtAE0AZQBkAGkASQB0AGEAbABWAGUAcgBzAGkAbwBuACAAMAAuADEAMQBVAG4AawBuAG8AdwBuAFUAbgBrAG4AbwB3AG4AVQBuAGsAbgBvAHcAbgADAAD/8LMzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA);
+ }
+ </style>
+</head>
+<body>
+ <canvas id="canvas" width="200" height="200"></canvas>
+ <script>
+ let canvas = document.getElementById('canvas');
+ canvas.mozPrintCallback = (obj) => {
+ let ctx = obj.context;
+ // Intentionally use a different fallback font than the test file so that
+ // if the font fails to load the test and reference will still be
+ // different.
+ ctx.font = '10px test, sans-serif';
+ ctx.fillText("lmnop", 20, 20);
+ obj.done();
+ window.postMessage("ready", "*");
+ };
+ </script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/printpreview_helper.xhtml b/layout/base/tests/chrome/printpreview_helper.xhtml
new file mode 100644
index 0000000000..e40d2e4d5b
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_helper.xhtml
@@ -0,0 +1,1721 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<window onload="runTests()"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <iframe style="min-height: 200px; min-width: 600px" type="content"></iframe>
+ <iframe style="min-height: 200px; min-width: 600px" type="content"></iframe>
+<script type="application/javascript">
+<![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
+var is = window.arguments[0].is;
+var isnot = window.arguments[0].isnot;
+var ok = window.arguments[0].ok;
+var todo = window.arguments[0].todo;
+var info = window.arguments[0].info;
+var SimpleTest = window.arguments[0].SimpleTest;
+var gWbp;
+var gPrintPreviewWindow;
+var gPrintPreviewBrowser;
+var ctx1;
+var ctx2;
+var counter = 0;
+
+var file = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+filePath = file.path;
+
+function printpreview(options = {}) {
+ let resolve;
+ let promise = new Promise(r => { resolve = r });
+ var listener = {
+ onLocationChange: function(webProgress, request, location, flags) { },
+ onProgressChange: function(webProgress, request, curSelfProgress,
+ maxSelfProgress, curTotalProgress,
+ maxTotalProgress) {
+ info("onProgressChange", [...arguments].join(", "));
+ },
+ onSecurityChange: function(webProgress, request, state) { },
+ onStateChange: function(webProgress, request, stateFlags, status) {
+ info("onStateChange", [...arguments].join(", "));
+ if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP) {
+ setTimeout(resolve, 0);
+ }
+ },
+ onStatusChange: function(webProgress, request, status, message) {
+ info("onStatusChange", [...arguments].join(", "));
+ },
+ onContentBlockingEvent: function(webProgress, request, event) {
+ info("onContentBlockingEvent", [...arguments].join(", "));
+ },
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsISupportsWeakReference))
+ return this;
+ throw Components.Exception("", Cr.NS_NOINTERFACE);
+ }
+ }
+ var settings = Cc["@mozilla.org/gfx/printsettings-service;1"]
+ .getService(Ci.nsIPrintSettingsService).createNewPrintSettings();
+ settings.printBGColors = true;
+ settings.headerStrLeft = "";
+ settings.headerStrRight = "";
+ settings.footerStrLeft = "";
+ settings.footerStrRight = "";
+ settings.unwriteableMarginTop = 0;
+ settings.unwriteableMarginRight = 0;
+ settings.unwriteableMarginLeft = 0;
+ settings.unwriteableMarginBottom = 0;
+ if (options.settings) {
+ for (let key in options.settings) {
+ settings[key] = options.settings[key];
+ }
+ }
+ var before = 0;
+ var after = 0;
+ function beforeprint() { ++before; }
+ function afterprint() { ++after; }
+ frameElts[0].contentWindow.addEventListener("beforeprint", beforeprint, true);
+ frameElts[0].contentWindow.addEventListener("afterprint", afterprint, true);
+ {
+ let bc = frameElts[0].contentWindow.browsingContext;
+ let browser = document.createXULElement("browser");
+ browser.setAttribute("type", "content");
+ browser.style.minHeight = "800px";
+ browser.style.maxWidth = browser.style.minWidth = "800px";
+ browser.setAttribute("initialBrowsingContextGroupId", bc.group.id);
+ browser.setAttribute("nodefaultsrc", "true");
+ document.documentElement.appendChild(browser);
+ gPrintPreviewBrowser = browser;
+
+ // Force docViewer creation and layout.
+ browser.browsingContext.docShell.document;
+ browser.getBoundingClientRect();
+
+ gPrintPreviewWindow = frameElts[0].contentWindow.printPreview(settings, listener, browser.browsingContext.docShell);
+ }
+ gWbp = gPrintPreviewWindow.docShell.docViewer;
+ gWbp.QueryInterface(Ci.nsIWebBrowserPrint);
+ is(before, 1, "Should have called beforeprint listener!");
+ if (!options.hasMozPrintCallback) {
+ // If there's a mozPrintCallback the after print event won't fire until
+ // later.
+ is(after, 1, "Should have called afterprint listener!");
+ }
+ frameElts[0].contentWindow.removeEventListener("beforeprint", beforeprint, true);
+ frameElts[0].contentWindow.removeEventListener("afterprint", afterprint, true);
+ return promise;
+}
+
+function exitprintpreview() {
+ gPrintPreviewWindow.docShell.exitPrintPreview();
+ gPrintPreviewBrowser.remove();
+}
+
+function finish() {
+ SimpleTest.finish();
+ window.close();
+}
+
+async function runTests()
+{
+ // This ensures we actually test the lazy-load of images in printpreview_images.
+ await SpecialPowers.pushPrefEnv({
+ 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],
+ ],
+ });
+ startTest1();
+}
+
+function compareCanvases(options = {}) {
+ const canvas1 = document.getElementsByTagName("canvas")[0];
+ const canvas2 = document.getElementsByTagName("canvas")[1];
+ let maxDifference = {};
+ const differingPixels = window.windowUtils.compareCanvases(canvas1, canvas2, maxDifference);
+ if (differingPixels) {
+ todo(false, "different: " + differingPixels + ", maxDifference: " + maxDifference.value);
+ todo(false, "TEST CASE: " + canvas1.toDataURL());
+ todo(false, "REFERENCE: " + canvas2.toDataURL());
+ }
+
+ let maxAllowedDifferent = options.maxDifferent || 0;
+ let maxAllowedDifference = options.maxDifference || 0;
+ return differingPixels <= maxAllowedDifferent && maxDifference.value <= maxAllowedDifference;
+}
+
+function addHTMLContent(parent) {
+ var n = parent.ownerDocument.createElement("div");
+ parent.appendChild(n);
+ var s = "<iframe width='500' height='40' src='data:text/plain,ThisIsAnIframeCreatedDuringPrintPreview'></iframe>";
+ s += "<table>";
+ for (var i = 1; i < 501; ++i) {
+ s += "<tr><td>Cell A" + i + "</td><td>Cell B" + i + "</td><td>Cell C" + i + "</td></tr>";
+ }
+ s += "</table>";
+ n.innerHTML = s;
+}
+
+async function startTest1() {
+ ctx1 = document.getElementsByTagName("canvas")[0].getContext("2d");
+ ctx2 = document.getElementsByTagName("canvas")[1].getContext("2d");
+ frameElts[0].contentDocument.body.innerHTML = "<div> </div><div>" + counter + " timers</div><div> </div>";
+
+ // Note this timeout is needed so that we can check that timers run
+ // after print preview, but not during it.
+ frameElts[0].contentWindow.wrappedJSObject.counter = counter;
+ frameElts[0].contentWindow.counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
+ "window.setTimeout(counterTimeout, 0);";
+ frameElts[0].contentWindow.setTimeout(frameElts[0].contentWindow.counterTimeout, 0);
+ frameElts[0].contentDocument.body.firstChild.innerHTML = "Print preview";
+
+ await printpreview();
+ drawPrintPreviewWindow(ctx1);
+ frameElts[0].contentDocument.body.firstChild.innerHTML = "Galley presentation";
+
+ // Add some elements.
+ addHTMLContent(frameElts[0].contentDocument.body.lastChild);
+ // Delete them.
+ frameElts[0].contentDocument.body.lastChild.innerHTML = "";
+ // And readd.
+ addHTMLContent(frameElts[0].contentDocument.body.lastChild);
+
+ setTimeout(finalizeTest1, 1000);
+}
+
+function finalizeTest1() {
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+ ok(compareCanvases(), "Canvas should be the same!");
+ counter = frameElts[0].contentWindow.counter;
+ // This timeout is needed so that we can check that timers do run after
+ // print preview.
+ setTimeout(runTest2, 1000);
+}
+
+function runTest2() {
+ isnot(frameElts[0].contentDocument.body.firstChild.nextSibling.textContent, "0 timers", "Timers should have run!");
+ isnot(frameElts[0].contentWindow.counter, 0, "Timers should have run!");
+ counter = frameElts[0].contentWindow.counter;
+ frameElts[0].contentWindow.counterTimeout = "";
+ setTimeout(runTest3, 0);
+}
+
+var elementIndex = 0;
+var compareEmptyElement = true;
+var emptyFormElements =
+ ["<input type='text'>",
+ "<input type='password'>",
+ "<input type='file'>",
+ "<input type='button'>",
+ "<input type='submit'>",
+ "<input type='reset'>",
+ "<input type='checkbox'>",
+ "<input type='radio'>",
+ "<select></select>",
+ "<select size='5'></select>",
+ "<textarea></textarea>"];
+
+var formElements =
+ ["<input type='text' value='text'>",
+ "<input type='password' value='password'>",
+ "<input type='file' value='" + filePath + "'>",
+ "<input type='button' value='button'>",
+ "<input type='submit' value='submit button'>",
+ "<input type='reset' value='reset button'>",
+ "<input type='checkbox' checked>",
+ "<input type='radio' checked>",
+ "<select><option>option1</option></select>",
+ "<select size='5'><option>1</option><option>2</option><option>3</option></select>",
+ "<textarea value='textarea'>textarea</textarea>"];
+
+function runTest3() {
+ if (compareEmptyElement) {
+ var currentIndex = elementIndex;
+ ++elementIndex;
+ if (elementIndex >= emptyFormElements.length) {
+ elementIndex = 0;
+ compareEmptyElement = false;
+ }
+ compareFormElementPrint(emptyFormElements[currentIndex], emptyFormElements[currentIndex], true);
+ return;
+ } else if (elementIndex < emptyFormElements.length) {
+ var currentIndex = elementIndex;
+ ++elementIndex;
+ compareFormElementPrint(emptyFormElements[currentIndex], formElements[currentIndex], false);
+ return;
+ }
+
+ setTimeout(runTest4, 0)
+}
+
+async function compareFormElementPrint(el1, el2, equals) {
+ frameElts[0].contentDocument.body.innerHTML = el1;
+ frameElts[0].contentDocument.body.firstChild.value =
+ frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+ await printpreview();
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+ frameElts[0].contentDocument.body.innerHTML = el2;
+ frameElts[0].contentDocument.body.firstChild.value =
+ frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+ await printpreview();
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+ is(compareCanvases(), equals,
+ "Comparing print preview didn't succeed [" + el1 + " : " + el2 + "]");
+ setTimeout(runTest3, 100);
+}
+
+// This is a crash test for bug 539060.
+function runTest4() {
+ frameElts[0].contentDocument.body.innerHTML =
+ "<iframe style='display: none;' src='data:text/html,<iframe>'></iframe>";
+ setTimeout(runTest4end, 500);
+}
+
+async function runTest4end() {
+ await printpreview();
+ exitprintpreview();
+
+ runTest5();
+}
+
+// This is a crash test for bug 595337
+async function runTest5() {
+ frameElts[0].contentDocument.body.innerHTML =
+ '<iframe style="position: fixed; visibility: hidden; bottom: 10em;"></iframe>' +
+ '<input contenteditable="true" style="display: table; page-break-before: left; width: 10000px;">';
+ await printpreview();
+ exitprintpreview();
+
+ setTimeout(runTest6, 0);
+}
+
+// Crash test for bug 878037
+function runTest6() {
+ frameElts[0].contentDocument.body.innerHTML =
+ '<style> li { list-style-image: url("animated.gif"); } </style>' +
+ '<li>Firefox will crash if you try and print this page</li>';
+
+ setTimeout(runTest6end, 500);
+}
+
+async function runTest6end() {
+ await printpreview();
+ exitprintpreview();
+
+ requestAnimationFrame(function() { setTimeout(runTest7); } );
+}
+
+async function runTest7() {
+ var contentText = "<a href='#'>mozilla</a><input>test<select><option>option1</option></select>";
+ // Create normal content
+ frameElts[0].contentDocument.body.innerHTML =
+ "<div>" + contentText + "</div>";
+ frameElts[0].contentDocument.body.firstChild.value =
+ frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+ await printpreview();
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+
+ frameElts[0].contentDocument.body.innerHTML = "<div></div>";
+ var sr = frameElts[0].contentDocument.body.firstChild.attachShadow({mode: "open"});
+ sr.innerHTML = contentText;
+ await printpreview();
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+ ok(compareCanvases(), "Printing light DOM and shadow DOM should create same output");
+
+ requestAnimationFrame(function() { setTimeout(runTest8); } );
+}
+
+async function runTest8() {
+ // Test that fonts loaded with CSS and JS are printed the same.
+ const iframeElement = document.getElementsByTagName("iframe")[0];
+
+ // First, snapshot the page with font defined in CSS.
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("load", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", "printpreview_font_api_ref.html");
+ });
+ await printpreview();
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+
+ // Second, snapshot the page with font loaded in JS.
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("message", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", "printpreview_font_api.html");
+ });
+ await printpreview();
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+ ok(compareCanvases(), "Printing pages with fonts loaded from CSS and JS should be the same.");
+
+ requestAnimationFrame(function() { setTimeout(runTest9); } );
+}
+
+// Test for bug 1487649
+async function runTest9() {
+ frameElts[0].contentDocument.body.innerHTML = `
+ <svg width="100" height="100">
+ <rect width='100' height='100' fill='lime'/>
+ </svg>
+ `;
+
+ await printpreview();
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+
+ frameElts[0].contentDocument.body.innerHTML = `
+ <svg width="100" height="100">
+ <defs>
+ <g id="useme">
+ <rect width='100' height='100' fill='lime'/>
+ </g>
+ </defs>
+ <use />
+ </svg>
+ `;
+
+ // Set the attribute explicitly because this is a chrome document, and the
+ // href attribute would get sanitized.
+ frameElts[0].contentDocument.querySelector("use").setAttribute("href", "#useme");
+
+ // Ensure the <use> shadow tree is created so we test what we want to test.
+ frameElts[0].contentDocument.body.offsetTop;
+
+ await printpreview();
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+ ok(compareCanvases(), "Printing <use> subtrees should create same output");
+
+ requestAnimationFrame(function() { setTimeout(runTest10); } );
+}
+
+function drawPrintPreviewWindow(ctx) {
+ let width = gPrintPreviewWindow.innerWidth;
+ let height = gPrintPreviewWindow.innerHeight;
+ ctx.canvas.width = width;
+ ctx.canvas.height = height;
+ ctx.drawWindow(gPrintPreviewWindow, 0, 0, width, height, "rgb(255, 255, 255)");
+}
+
+// Test for bug 1524640
+async function runTest10() {
+ // Test that fonts loaded during mozprint callback are loaded into the cloned
+ // document.
+ const iframeElement = document.getElementsByTagName("iframe")[0];
+
+ // First, snapshot the page with font defined in CSS.
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("load", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", "printpreview_font_mozprintcallback_ref.html");
+ });
+ let mozPrintCallbackDone = new Promise((resolve) => {
+ iframeElement.addEventListener("message", resolve, { capture: true, once: true });
+ });
+ await printpreview({ hasMozPrintCallback: true });
+ await mozPrintCallbackDone;
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+
+ // Second, snapshot the page with font loaded in JS.
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("load", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", "printpreview_font_mozprintcallback.html");
+ });
+ mozPrintCallbackDone = new Promise((resolve) => {
+ iframeElement.addEventListener("message", resolve, { capture: true, once: true });
+ });
+ await printpreview({ hasMozPrintCallback: true });
+ // Wait for the mozprintcallback to finish.
+ await mozPrintCallbackDone;
+ drawPrintPreviewWindow(ctx2);
+
+ exitprintpreview();
+ ok(compareCanvases(), "Printing pages with fonts loaded from a mozPrintCallback should be the same.");
+
+ requestAnimationFrame(function() { setTimeout(runTest11); } );
+}
+
+async function compareFiles(src1, src2, options = {}) {
+ const BASE = "https://example.org/chrome/layout/base/tests/chrome/";
+
+ info(`Comparing ${src1} with ${src2}`);
+ const iframeElement = document.getElementsByTagName("iframe")[0];
+
+ let messagePromise = null;
+ if (options.waitForMessage) {
+ messagePromise = new Promise(resolve => {
+ iframeElement.addEventListener("message", resolve, { capture: true, once: true });
+ });
+ }
+
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("load", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", new URL(src1, BASE).href);
+ });
+ let mediaElements = iframeElement.contentDocument.querySelectorAll(
+ "audio, video"
+ );
+ for (let mediaElement of mediaElements) {
+ let { widget } = SpecialPowers.wrap(iframeElement.contentWindow)
+ .windowGlobalChild.getActor("UAWidgets")
+ .widgets.get(mediaElement);
+ await widget.impl.Utils.l10n.translateRoots();
+ }
+
+ if (messagePromise) {
+ info("awaiting for message to arrive");
+ await messagePromise;
+ }
+
+ await printpreview(options.test || options);
+ drawPrintPreviewWindow(ctx1);
+ exitprintpreview();
+
+ await new Promise((resolve) => {
+ iframeElement.addEventListener("load", resolve, { capture: true, once: true });
+ iframeElement.setAttribute("src", new URL(src2, BASE).href);
+ });
+ mediaElements = iframeElement.contentDocument.querySelectorAll(
+ "audio, video"
+ );
+ for (let mediaElement of mediaElements) {
+ let { widget } = SpecialPowers.wrap(iframeElement.contentWindow)
+ .windowGlobalChild.getActor("UAWidgets")
+ .widgets.get(mediaElement);
+ await widget.impl.Utils.l10n.translateRoots();
+ }
+
+ await printpreview(options.ref || options);
+ drawPrintPreviewWindow(ctx2);
+ exitprintpreview();
+
+ is(compareCanvases(options), !options.expectDifference, `Printing ${src1} and ${src2} should${options.expectDifference ? ' not' : ''} produce the same results`);
+}
+
+// bug 1567105
+async function runTest11() {
+ await compareFiles("printpreview_quirks.html", "printpreview_quirks_ref.html");
+ requestAnimationFrame(function() { setTimeout(runTest12); } );
+}
+
+// bug 1621415
+async function runTest12() {
+ await compareFiles("test_document_adopted_styles.html", "test_document_adopted_styles_ref.html");
+ requestAnimationFrame(function() { setTimeout(runTest13); } );
+}
+
+// bug 1621415
+async function runTest13() {
+ await compareFiles("test_shadow_root_adopted_styles.html", "test_shadow_root_adopted_styles_ref.html");
+ requestAnimationFrame(function() { setTimeout(runTest14); } );
+}
+
+// bug 1622322
+async function runTest14() {
+ await compareFiles("test_shared_adopted_styles.html", "test_shared_adopted_styles_ref.html");
+ requestAnimationFrame(function() { setTimeout(runTest15); } );
+}
+
+// Crash test for bug 1615261
+async function runTest15() {
+ frameElts[0].contentDocument.body.innerHTML =
+ '<style>div { width: 100px; height: 100px; background-image: url("animated.gif"); } </style>' +
+ '<div>Firefox will crash if you try and print this page</div>';
+
+ // XXX Is there a more reliable way to wait for the background-image to load?
+ await new Promise(resolve => setTimeout(resolve, 500));
+
+ await printpreview();
+ await exitprintpreview();
+
+ requestAnimationFrame(function() { setTimeout(runTest16); } );
+}
+
+// Various image tests.
+async function runTest16() {
+ // fuzzy: SVG image in the test pixel-snaps different than <div> in the ref.
+ // (And on WebRender, the pixel-snapping seems to shift some pixels over a
+ // bit such that they're fully white vs. fully blue; hence 255 as the allowed
+ // color-channel difference.)
+ // XXXdholbert We should revisit this and adjust these thresholds (hopefully
+ // lower) after bug 1602410 lands.
+ await compareFiles("printpreview_images.html", "printpreview_images_ref.html", { maxDifferent: 118, maxDifference: 255 });
+ requestAnimationFrame(function() { setTimeout(runTest17); } );
+}
+
+async function runTest17() {
+ // fuzzy: SVG image in the test pixel-snaps different than <div> in the ref.
+ // (And on WebRender, the pixel-snapping seems to shift some pixels over a
+ // bit such that they're fully white vs. fully blue; hence 255 as the allowed
+ // color-channel difference.)
+ // XXXdholbert We should revisit this and adjust these thresholds (hopefully
+ // lower) after bug 1602410 lands.
+ await compareFiles("printpreview_images_sw.html", "printpreview_images_sw_ref.html", { waitForMessage: true, maxDifferent: 118, maxDifference: 255 });
+ requestAnimationFrame(() => setTimeout(runTest18));
+}
+
+async function runTest18() {
+ await compareFiles("printpreview_quirks.html", "printpreview_quirks_ref.html", {
+ settings: {
+ marginTop: 22,
+ marginBottom: 22,
+ marginLeft: 22,
+ marginRight: 22,
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest19));
+}
+
+async function runTest19() {
+ await compareFiles("color_adjust.html", "color_adjust_ref.html", {
+ test: {
+ settings: {
+ printBGColors: false,
+ printBGImages: false,
+ },
+ },
+ ref: {
+ settings: {
+ printBGColors: true,
+ printBGImages: true,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest20));
+}
+
+async function runTest20() {
+ frameElts[0].contentDocument.body.innerHTML =
+ '<style>div { page-break-after: always; }</style>' +
+ '<div>1</div>' +
+ '<div>2</div>' +
+ '<div>3</div>';
+ await printpreview();
+
+ is(gWbp.printPreviewCurrentPageNumber, 1,
+ "The initial current page number should be 1");
+
+ // Scroll to the second page.
+ gWbp.printPreviewScrollToPage(Ci.nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM, 2);
+
+ is(gWbp.printPreviewCurrentPageNumber, 2,
+ "The current page number should be 2");
+
+ // Scroll to the last page.
+ gWbp.printPreviewScrollToPage(Ci.nsIWebBrowserPrint.PRINTPREVIEW_END, 0);
+
+ is(gWbp.printPreviewCurrentPageNumber, 3,
+ "The current page number should be 3");
+
+ exitprintpreview();
+
+ requestAnimationFrame(() => setTimeout(runTest21));
+}
+
+async function runTest21() {
+ await compareFiles("data:text/html,<audio controls>", "data:text/html,<audio controls >"); // Shouldn't crash.
+ requestAnimationFrame(() => setTimeout(runTest22));
+}
+
+async function runTest22() {
+ // Similar to above runtTest20 but more specific for the logic to choose
+ // the current page in the new print preview UI so this test works only
+ // in the new print preview.
+ frameElts[0].contentDocument.body.innerHTML =
+ '<style>div { page-break-after: always; max-height: 2in; }</style>' +
+ '<div>1</div>' +
+ '<div>2</div>' +
+ '<div>3</div>' +
+ '<div>4</div>' +
+ '<div>5</div>' +
+ '<div>6</div>' +
+ '<div>7</div>' +
+ '<div>8</div>' +
+ '<div>9</div>' +
+ '<div>10</div>';
+
+ await printpreview({ settings: { paperHeight: 3 } });
+
+ const initialCurrentPageNumber = gWbp.printPreviewCurrentPageNumber;
+
+ // NOTE: In the cases the page hight is less than the half height of the
+ // print preview scroll port height, the initial current page number will
+ // not be 1.
+ ok(initialCurrentPageNumber >= 1,
+ "The initial current page number should be equal to or greater than 1");
+
+ const totalPageNumber = gWbp.printPreviewNumPages;
+ for (let n = initialCurrentPageNumber;
+ n <= totalPageNumber - initialCurrentPageNumber;
+ n++) {
+ // Scroll to the given page number and check the current page number.
+ gWbp.printPreviewScrollToPage(
+ Ci.nsIWebBrowserPrint.PRINTPREVIEW_GOTO_PAGENUM, n);
+ is(gWbp.printPreviewCurrentPageNumber, n,
+ `The current page number should be ${n}`);
+ }
+
+ // Scroll to the end of the scroll region.
+ gWbp.printPreviewScrollToPage(Ci.nsIWebBrowserPrint.PRINTPREVIEW_END, 0);
+
+ // Same as the initial current page number case, the last page might not
+ // be the current page if the page height is less than the half of the scroll
+ // port.
+ is(gWbp.printPreviewCurrentPageNumber,
+ totalPageNumber + 1 - initialCurrentPageNumber,
+ `The current page number should be ${totalPageNumber + 1 - initialCurrentPageNumber}`);
+
+ exitprintpreview();
+
+ requestAnimationFrame(() => setTimeout(runTest23));
+}
+
+async function runTest23() {
+ await compareFiles("printpreview_prettyprint.xml", "printpreview_prettyprint_ref.xhtml");
+ requestAnimationFrame(() => setTimeout(runTest24));
+}
+async function runTest24() {
+ await compareFiles("printpreview_mask.html", "data:text/html,", {
+ settings: {
+ printBGColors: false,
+ printBGImages: false,
+ },
+ expectDifference: true,
+ });
+ requestAnimationFrame(() => setTimeout(runTest25));
+}
+
+async function runTest25() {
+ await compareFiles("printpreview_downloadable_font.html", "printpreview_downloadable_font_ref.html");
+ requestAnimationFrame(() => setTimeout(runTest26));
+}
+
+async function runTest26() {
+ await compareFiles("printpreview_downloadable_font_in_iframe.html", "printpreview_downloadable_font_in_iframe_ref.html");
+ requestAnimationFrame(() => setTimeout(runTest27));
+}
+
+async function runTest27() {
+ await compareFiles("data:text/html,<style>:root { background-color: red; background-image: linear-gradient(red, red) }</style>", "data:text/html,", {
+ settings: {
+ printBGColors: false,
+ printBGImages: false,
+ }
+ });
+ requestAnimationFrame(() => setTimeout(runTest28));
+}
+
+async function runTest28() {
+ await compareFiles("data:text/html,<style>@page { margin: 0 }</style>Foo", "data:text/html,Foo", {
+ settings: {
+ honorPageRuleMargins: false,
+ marginTop: 1,
+ }
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest29));
+}
+
+async function runTest29() {
+ await compareFiles("data:text/html,<style>@page { margin: 0 }</style>Foo", "data:text/html,Foo", {
+ settings: {
+ honorPageRuleMargins: true,
+ marginTop: 1,
+ },
+ expectDifference: true,
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest30));
+}
+
+// Helper function to test trivial/unsupported pages-per-sheet values which we
+// just treat as 1 page-per-sheet (as if the attribute were unset.)
+// NOTE: The second data-URI's explicit "<body>" tag is not meant to affect the
+// rendering -- it's just a hack to ensure that the URIs themselves are
+// different, so that compareFiles() sees the two URIs as different and gets
+// the "load" notification that it depends on after switching between them.
+// We also include numPages in the tested URL/content here, so that if we get
+// a test-failure, it's easy to figure out which call to this function had the
+// failure.
+async function checkTrivialPagesPerSheetValue(numPages) {
+ let stringToDisplay = "TrivialPagesPerSheetVal" + numPages;
+ await compareFiles("data:text/html," + stringToDisplay,
+ "data:text/html,<body>" + stringToDisplay, {
+ test: {
+ settings: {
+ numPagesPerSheet: numPages,
+ },
+ },
+ ref: { settings: {} },
+ });
+}
+
+async function runTest30() {
+ await checkTrivialPagesPerSheetValue(1);
+ await checkTrivialPagesPerSheetValue(0);
+ await checkTrivialPagesPerSheetValue(-5);
+ await checkTrivialPagesPerSheetValue(7);
+ await checkTrivialPagesPerSheetValue(500);
+
+ requestAnimationFrame(() => setTimeout(runTest31));
+}
+
+// Helper function to test supported pages-per-sheet values that actually do
+// tiling (i.e. values greater than 1). We render the testcase and reference
+// case with zero page-margins and zero unwritable margins. (This makes it
+// tractable to create a reference case without having to account for margins
+// that are outside of the content area.)
+async function checkSupportedPagesPerSheetValue(src1, src2, numPages, fuzz) {
+ await compareFiles(src1, src2, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ numPagesPerSheet: numPages,
+ },
+ },
+ ref: {
+ settings: {
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+}
+
+// Pages-per-sheet: test the supported values.
+// First we test the perfect-square values: 4, 9, 16.
+// Then we test the other values, 2 and 6. (They require some extra bespoke
+// configuration to mock up their page-rotation, so their runTest functions are
+// a bit more verbose.)
+async function runTest31() {
+ // XXXdholbert On windows, our zero-margin settings aren't reliably respected
+ // for some reason; see bug 1680838. For now, we just account for that with a
+ // hefty amount of fuzz, guarded behind a platform-specific check so that we
+ // can keep this strict on other platforms.
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 101278, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ await checkSupportedPagesPerSheetValue("printpreview_pps4.html",
+ "printpreview_pps4_ref.html", 4, fuzz);
+
+ requestAnimationFrame(() => setTimeout(runTest32));
+}
+async function runTest32() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 130170, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ await checkSupportedPagesPerSheetValue("printpreview_pps9.html",
+ "printpreview_pps9_ref.html", 9, fuzz);
+
+ requestAnimationFrame(() => setTimeout(runTest33));
+}
+async function runTest33() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 145706, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ await checkSupportedPagesPerSheetValue("printpreview_pps16.html",
+ "printpreview_pps16_ref.html", 16,
+ fuzz);
+
+ requestAnimationFrame(() => setTimeout(runTest34));
+}
+
+async function runTest34() {
+ let fuzz = navigator.platform.includes("Win") ? // Workaround for bug 1680838
+ { maxDifferent: 44256, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "printpreview_pps2.html";
+ let ref = "printpreview_pps2_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ numPagesPerSheet: 2,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ orientation: 1, /* Landscape mode */
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest35));
+}
+
+async function runTest35() {
+ let fuzz = navigator.platform.includes("Win") ? // Workaround for bug 1680838
+ { maxDifferent: 88751, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "printpreview_pps6.html";
+ let ref = "printpreview_pps6_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 6,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ numPagesPerSheet: 6,
+ orientation: 1, /* Landscape mode */
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 6,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest36));
+}
+
+// Testcases for pages-per-sheet with nonzero unwriteable margin values:
+// ---------------------------------------------------------------------
+
+// In this subtest, the vertical scale-factor is more-severe and hence ends up
+// "winning", and we have a bit of extra space in the horizontal axis which we
+// distribute equally on either side (see the _ref.html file used below for
+// more details).
+async function runTest36() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 139464, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "printpreview_pps_uw4.html";
+ let ref = "printpreview_pps_uw4_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0.6,
+ unwriteableMarginRight: 0.1,
+ unwriteableMarginBottom: 0.4,
+ unwriteableMarginLeft: 0.3,
+ numPagesPerSheet: 4,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest37));
+}
+
+// In this subtest, the horizontal scale-factor is more-severe and hence ends
+// up "winning", and we have a bit of extra space in the vertical axis which we
+// distribute equally on either side (see the _ref.html file used below for
+// more details).
+async function runTest37() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 152268, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "printpreview_pps_uw9.html";
+ let ref = "printpreview_pps_uw9_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0.2,
+ unwriteableMarginRight: 0.8,
+ unwriteableMarginBottom: 0.4,
+ unwriteableMarginLeft: 1.2,
+ numPagesPerSheet: 9,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 10,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest38));
+}
+
+async function runTest38() {
+ let fuzz = navigator.platform.includes("Win") ? // Workaround for bug 1680838
+ { maxDifferent: 117744, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "printpreview_pps_uw2.html";
+ let ref = "printpreview_pps_uw2_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0.8,
+ unwriteableMarginRight: 0.6,
+ unwriteableMarginBottom: 1.2,
+ unwriteableMarginLeft: 0.4,
+ numPagesPerSheet: 2,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ /* Note: These are the same values we used for 'test' above, except
+ that here we've rotated the margins counterclockwise through the
+ sides, to account for the fact that we're specifying these margins
+ for a landscape-orientation page here vs. portrait-mode above.*/
+ unwriteableMarginTop: 0.6,
+ unwriteableMarginRight: 1.2,
+ unwriteableMarginBottom: 0.4,
+ unwriteableMarginLeft: 0.8,
+ orientation: 1, /* Landscape mode */
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest39));
+}
+
+// In this subtest, the vertical unwriteable margins exactly consume the full
+// pageHeight, so we don't have any space available for printing and we just
+// print a blank sheet. (This is mostly a stress test to be sure we don't
+// crash, hang, divide-by-zero, etc. in this edge case.)
+async function runTest39() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 254, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "data:text/html,Unwriteable-Margins-Too-Tall-To-See-This";
+ let ref = "data:text/html,<!-- runTest39 -->";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginTop: 3,
+ unwriteableMarginBottom: 2,
+ numPagesPerSheet: 4,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest40));
+}
+
+// In this subtest, the horizontal unwriteable margins consume more than the
+// full pageWidth, so we don't have any space available for printing and we
+// just print a blank sheet. (This is mostly a stress test to be sure we don't
+// crash, hang, divide-by-zero, etc. in this edge case.)
+async function runTest40() {
+ let fuzz = navigator.platform.includes("Win") ?
+ { maxDifferent: 172, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ let test = "data:text/html,Unwriteable-Margins-Too-Wide-To-See-This";
+ let ref = "data:text/html,<!-- runTest40 -->";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginRight: 3,
+ unwriteableMarginLeft: 4,
+ numPagesPerSheet: 9,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 4,
+ paperHeight: 5,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest41));
+}
+
+// Drawing headers/footers with very large unwriteable margins. The specific
+// bug occurs when combined unwriteable are larger than half of the page's
+// dimensions.
+async function runTest41() {
+ // This test compares a rendered page to the headers/footers that print code
+ // generates. On Windows, workaround bug 1680838. On OS X, the headers/
+ // footers are sometimes slightly offset. See bug 1714217.
+ // It's not too big a deal to have a higher fuzz factor, since when
+ // bug 1713404 occurs no headers/footers at all are rendered. These higher
+ // fuzz factors will still catch this worst case on OS X.
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 133, maxDifference: 255 }; // Bug 1680838
+ }
+ else if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 60, maxDifference: 200 }; // Bug 1714217
+ }
+ else {
+ var fuzz = { maxDifferent: 14, maxDifference: 16 };
+ }
+
+ let test = "data:text/html,<!-- runTest41 -->";
+ let ref = "printpreview_bug1713404_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 2,
+ unwriteableMarginRight: 2,
+ unwriteableMarginTop: 2,
+ unwriteableMarginBottom: 2,
+ headerStrLeft: "|",
+ headerStrRight: "||",
+ footerStrLeft: "|||",
+ footerStrRight: "||||",
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginBottom: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest42));
+}
+
+// Test that @page{ size: ... } works correctly with a smaller specified
+// @page{ size: ... } than the paper we are printing to. The test and ref use
+// varying margin and size properties to place elements in the same location.
+// This depends on Firefox's current behavior of putting undersized pages into
+// the upper left corner, rather than scaling or centering the page.
+async function runTest42() {
+ if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 15, maxDifference: 8 };
+ }
+ let test = "print_page_size1.html";
+ let ref = "print_page_size1_ref.html";
+ await compareFiles(test, ref, fuzz);
+
+ requestAnimationFrame(() => setTimeout(runTest43));
+}
+
+// Test that @page{ size: ... } works correctly with a larger specified
+// @page{ size: ... } than the paper we are printing to. This specifically
+// tests scaling when only one page edge is too large.
+// This depends on Firefox's current behavior of scaling down any oversized
+// pages to fit onto a single physical page, and putting this aligned to the
+// upper left corner rather than centering the page.
+async function runTest43() {
+ if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 15, maxDifference: 8 };
+ }
+ let test = "print_page_size2.html";
+ let ref = "print_page_size2_ref.html";
+ await compareFiles(test, ref, fuzz);
+
+ requestAnimationFrame(() => setTimeout(runTest44));
+}
+
+// Test that @page{ size: ... } works correctly with a smaller specified
+// @page{ size: ... } than the paper we are printing to. The test case uses
+// only the size property and the ref case uses only absolute positioning.
+// This depends on Firefox's current behavior of putting undersized pages into
+// the upper left corner, rather than scaling or centering the page.
+async function runTest44() {
+ if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 15, maxDifference: 8 };
+ }
+ let test = "print_page_size3.html";
+ let ref = "print_page_size3_ref.html";
+ await compareFiles(test, ref, fuzz);
+ requestAnimationFrame(() => setTimeout(runTest45));
+}
+
+// Test that @page{ size: ... } results in scaling down the contents to fit on
+// a smaller paper size.
+// This depends on Firefox's current behavior of scaling oversized pages down
+// to fit onto the paper size.
+async function runTest45() {
+ if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 15, maxDifference: 8 };
+ }
+ let test = "print_page_size4.html";
+ let ref = "print_page_size4_ref.html";
+ await compareFiles(test, ref, fuzz);
+ requestAnimationFrame(() => setTimeout(runTest46));
+}
+
+// Test that small elements don't get clipped from the bottom of the page when
+// using a < 1.0 scaling factor.
+async function runTest46() {
+ var fuzz = { maxDifferent: 0, maxDifference: 0 };
+ let test = "bug1722890.html";
+ let ref = "bug1722890_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ scaling: 0.5,
+ shrinkToFit: false
+ }
+ },
+ ref: {
+ settings: {
+ scaling: 1.0,
+ shrinkToFit: false
+ }
+ }
+ });
+ requestAnimationFrame(() => setTimeout(runTest47));
+}
+
+// Test for header/footer text clipping when printing with scaling factor.
+async function runTest47() {
+ // This test compares a rendered page to the headers/footers that print code
+ // generates. On Windows, workaround bug 1680838. On OS X, the headers/
+ // footers are sometimes slightly offset. See bug 1714217.
+ // It's not too big a deal to have a higher fuzz factor, since when
+ // bug 1730091 occurs most of the headers/footers are not rendered at all,
+ // and these fuzz factors will still catch that.
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 200, maxDifference: 255 }; // Bug 1680838
+ }
+ else if (navigator.platform.includes("Mac")) {
+ var fuzz = { maxDifferent: 180, maxDifference: 255 }; // Bug 1714217
+ }
+ else {
+ var fuzz = { maxDifferent: 6, maxDifference: 16 };
+ }
+
+ let test = "data:text/html,<!-- runTest47 -->";
+ let ref = "printpreview_bug1730091_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ scaling: 1.3,
+ shrinkToFit: false,
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginBottom: 0,
+ headerStrLeft: "||||",
+ headerStrRight: "||||",
+ footerStrLeft: "||||",
+ footerStrRight: "||||",
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginBottom: 0,
+ },
+ },
+ });
+ requestAnimationFrame(() => setTimeout(runTest48));
+}
+
+// Test that even when downscaling happens due to CSS page-size, the
+// unwriteable margins are in units applicable to the resulting page as it is
+// actually printed.
+// https://bugzilla.mozilla.org/1769161
+async function runTest48() {
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 816, maxDifference: 255 }; // Bug 1680838
+ } else {
+ var fuzz = { maxDifferent: 16, maxDifference: 255 };
+ }
+ let test = "bug1769161_1.html";
+ let ref = "bug1769161_1_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginTop: 1,
+ unwriteableMarginBottom: 1,
+ },
+ });
+ requestAnimationFrame(() => setTimeout(runTest49));
+}
+
+// Same as runTest48, but uses different scaling factors.
+async function runTest49() {
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 6472, maxDifference: 255 }; // Bug 1680838
+ } else {
+ var fuzz = { maxDifferent: 24, maxDifference: 255 };
+ }
+ let test = "bug1769161_2.html";
+ let ref = "bug1769161_2_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginTop: 1,
+ unwriteableMarginBottom: 1,
+ },
+ });
+ requestAnimationFrame(() => setTimeout(runTest50));
+}
+
+// Test that when downscaling happens due to CSS page-size, the unwriteable
+// margins are equivalent to the @page margins after those margins are scaled.
+// https://bugzilla.mozilla.org/1769161
+async function runTest50() {
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 816, maxDifference: 255 }; // Bug 1680838
+ } else {
+ var fuzz = { maxDifferent: 16, maxDifference: 255 };
+ }
+ let test = "bug1769161_3.html";
+ let ref = "bug1769161_3_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginTop: 1,
+ unwriteableMarginBottom: 1,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginBottom: 0,
+ },
+ },
+ });
+ requestAnimationFrame(() => setTimeout(runTest51));
+}
+
+// Same as runTest50, but uses different scaling factors.
+async function runTest51() {
+ if (navigator.platform.includes("Win")) {
+ var fuzz = { maxDifferent: 11764, maxDifference: 255 }; // Bug 1680838
+ } else {
+ var fuzz = { maxDifferent: 24, maxDifference: 255 };
+ }
+ let test = "bug1769161_4.html";
+ let ref = "bug1769161_4_ref.html";
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginTop: 1,
+ unwriteableMarginBottom: 1,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 5,
+ paperHeight: 5,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ unwriteableMarginLeft: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginBottom: 0,
+ },
+ },
+ });
+ requestAnimationFrame(() => setTimeout(runTest52));
+}
+
+async function runTest52() {
+ // Unwriteable margins can be ignored by setting the appropriate flag.
+ // Slightly different to avoid hang.
+ await compareFiles("data:text/html,Foo", "data:text/html,<div>Foo", {
+ maxDifferent: 0,
+ maxDifference: 0,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginBottom: 1,
+ unwriteableMarginLeft: 1,
+ ignoreUnwriteableMargins: true,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest53));
+}
+
+async function runTest53() {
+ // Ensure that even when unwriteable margins are set to be taken into
+ // account, page rule margins can override it.
+ await compareFiles(
+ "data:text/html,<style>@page { margin: 0 }</style>Foo",
+ "data:text/html,<div>Foo",
+ {
+ maxDifferent: 0,
+ maxDifference: 0,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginBottom: 1,
+ unwriteableMarginLeft: 1,
+ ignoreUnwriteableMargins: false,
+ honorPageRuleMargins: true,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest54));
+}
+
+async function runTest54() {
+ // `ignoreUnwriteableMargins` lets author-specified margins ignore
+ // unwriteable margins as well. Without this flag, the unwriteable
+ // margin is ignored iff `Margins: Default` is set (i.e.
+ // `honorPageRuleMargins` set) and the author specified CSS page
+ // margin is zero.
+ // Note: At least currently, both `ignoreUnwriteableMargins`
+ // and `honorPageRuleMargins` cannot be set through the printing UI.
+ // TODO: If this behaviour is desired is up for debate.
+ await compareFiles(
+ "data:text/html,<style>@page { margin: 0.1in }</style>Foo",
+ "data:text/html,<div>Foo",
+ {
+ maxDifferent: 0,
+ maxDifference: 0,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 1,
+ unwriteableMarginRight: 1,
+ unwriteableMarginBottom: 1,
+ unwriteableMarginLeft: 1,
+ ignoreUnwriteableMargins: true,
+ honorPageRuleMargins: true,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0.1,
+ marginRight: 0.1,
+ marginBottom: 0.1,
+ marginLeft: 0.1,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest55));
+}
+
+async function runTest55() {
+ let test = "printpreview_pps_uw2.html";
+ let ref = "printpreview_pps_uw2_no_margin_ref.html";
+ let fuzz = navigator.platform.includes("Win") ? // Workaround for bug 1680838
+ { maxDifferent: 12870, maxDifference: 255 } :
+ { maxDifferent: 0, maxDifference: 0 };
+
+ // Unwriteable margins are successfully ignored, if requested,
+ // for pages-per-sheet.
+ await compareFiles(test, ref, {
+ maxDifferent: fuzz.maxDifferent,
+ maxDifference: fuzz.maxDifference,
+ test: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ paperSizeUnit: Ci.nsIPrintSettings.kPaperSizeInches,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0.8,
+ unwriteableMarginRight: 0.6,
+ unwriteableMarginBottom: 1.2,
+ unwriteableMarginLeft: 0.4,
+ numPagesPerSheet: 2,
+ ignoreUnwriteableMargins: true,
+ },
+ },
+ ref: {
+ settings: {
+ paperWidth: 8,
+ paperHeight: 10,
+ marginTop: 0,
+ marginRight: 0,
+ marginBottom: 0,
+ marginLeft: 0,
+ unwriteableMarginTop: 0,
+ unwriteableMarginRight: 0,
+ unwriteableMarginBottom: 0,
+ unwriteableMarginLeft: 0,
+ orientation: 1, /* Landscape mode */
+ },
+ },
+ });
+
+ requestAnimationFrame(() => setTimeout(runTest56));
+}
+
+async function runTest56() {
+ await compareFiles("printpreview_image_select.html", "printpreview_image_select_ref.html");
+ requestAnimationFrame(() => setTimeout(runTest57));
+}
+
+// Tests that printing with mixed page sizes doesn't crash.
+// These tests can't actually compare any pages after the first, so this only
+// verifies that we don't crash reflowing.
+async function runTest57() {
+ let test = "printpreview_mixed_page_size_001.html";
+ // The params are just to give the file unique URLs.
+ await compareFiles(test + "?test", test + "?ref");
+ requestAnimationFrame(() => setTimeout(runTest58));
+}
+
+// As with runTest57, this is only testing for crashes.
+// This includes fixed-position content, as this is the only way to get content
+// within the same chain of continuations onto pages with different sizes.
+async function runTest58() {
+ let test = "printpreview_mixed_page_size_002.html";
+ // The params are just to give the file unique URLs.
+ await compareFiles(test + "?test", test + "?ref");
+ finish();
+}
+
+]]></script>
+<table style="border: 1px solid black;" xmlns="http://www.w3.org/1999/xhtml">
+<tr><th>Print preview canvas 1</th><th>Print preview canvas 2</th></tr>
+<tr>
+<td><canvas height="800" width="800"></canvas></td>
+<td><canvas height="800" width="800"></canvas></td>
+</tr></table>
+</window>
diff --git a/layout/base/tests/chrome/printpreview_image_select.html b/layout/base/tests/chrome/printpreview_image_select.html
new file mode 100644
index 0000000000..b61a40774d
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_image_select.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<body>
+ <picture>
+ <source width="200" srcset="red.png 1w, green.png 200w">
+ <img>
+ </picture>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_image_select_ref.html b/layout/base/tests/chrome/printpreview_image_select_ref.html
new file mode 100644
index 0000000000..7189a57642
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_image_select_ref.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<body>
+ <div style="width: 200px; height: 200px; background-color: green;"></div>
+</body>
diff --git a/layout/base/tests/chrome/printpreview_images.html b/layout/base/tests/chrome/printpreview_images.html
new file mode 100644
index 0000000000..6919002354
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_images.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<style>
+ img, object, svg, input { display: block }
+ div {
+ content: url(blue-32x32.png);
+ width: 32px;
+ height: 32px;
+ }
+</style>
+<div></div>
+<picture>
+ <source srcset="blue-32x32.png">
+ <img width=32 height=32>
+</picture>
+<picture>
+ <source srcset="blue-32x32.png" media="print">
+ <source srcset="animated.gif" media="not print">
+ <img width=32 height=32>
+</picture>
+<img src="blue-32x32.png" width=32 height=32>
+<object data="blue-32x32.png" width=32 height=32></object>
+<svg width="32" height="32">
+ <image x=0 y=0 href="blue-32x32.png" width=32 height=32></image>
+</svg>
+<input type="image" src="blue-32x32.png" width=32 height=32>
diff --git a/layout/base/tests/chrome/printpreview_images_ref.html b/layout/base/tests/chrome/printpreview_images_ref.html
new file mode 100644
index 0000000000..65a0df066c
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_images_ref.html
@@ -0,0 +1,15 @@
+<!doctype html>
+<style>
+ div {
+ width: 32px;
+ height: 32px;
+ background-color: blue;
+ }
+</style>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
diff --git a/layout/base/tests/chrome/printpreview_images_sw.html b/layout/base/tests/chrome/printpreview_images_sw.html
new file mode 100644
index 0000000000..78b8a0dd88
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_images_sw.html
@@ -0,0 +1,46 @@
+<!doctype html>
+<style>
+ img, object, svg, input { display: block }
+ div {
+ content: url(nonexistent.png?1);
+ width: 32px;
+ height: 32px;
+ }
+</style>
+<script>
+const WORKER = "printpreview_images_sw.js";
+if (location.href.includes("registered")) {
+ console.log("REGISTERED");
+ onload = function() {
+ postMessage("ready", "*");
+ }
+ onbeforeunload = function() {
+ navigator.serviceWorker.getRegistrations().then(function(registrations) {
+ for(let registration of registrations) {
+ registration.unregister()
+ }
+ })
+ navigator.serviceWorker.unregister(WORKER);
+ }
+} else {
+ navigator.serviceWorker.oncontrollerchange = function() {
+ location.href = location.href + "?registered";
+ };
+ navigator.serviceWorker.register(WORKER);
+}
+</script>
+<div></div>
+<picture>
+ <source srcset="nonexistent.png?2">
+ <img width=32 height=32>
+</picture>
+<picture>
+ <source srcset="nonexistent.png?3" media="print">
+ <source srcset="animated.gif" media="not print">
+ <img width=32 height=32>
+</picture>
+<img src="nonexistent.png?4" width=32 height=32>
+<svg width="32" height="32">
+ <image x=0 y=0 href="nonexistent.png?7" width=32 height=32></image>
+</svg>
+<input type="image" src="nonexistent.png?6" width=32 height=32>
diff --git a/layout/base/tests/chrome/printpreview_images_sw.js b/layout/base/tests/chrome/printpreview_images_sw.js
new file mode 100644
index 0000000000..bb0ab60b1f
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_images_sw.js
@@ -0,0 +1,11 @@
+self.addEventListener("fetch", event => {
+ if (event.request.url.includes("nonexistent.png")) {
+ event.respondWith(
+ fetch(event.request.url.replace("nonexistent.png", "blue-32x32.png"))
+ );
+ }
+});
+
+self.addEventListener("activate", event => {
+ event.waitUntil(clients.claim());
+});
diff --git a/layout/base/tests/chrome/printpreview_images_sw_ref.html b/layout/base/tests/chrome/printpreview_images_sw_ref.html
new file mode 100644
index 0000000000..2efb9e9199
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_images_sw_ref.html
@@ -0,0 +1,14 @@
+<!doctype html>
+<style>
+ div {
+ width: 32px;
+ height: 32px;
+ background-color: blue;
+ }
+</style>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
+<div></div>
diff --git a/layout/base/tests/chrome/printpreview_mask.html b/layout/base/tests/chrome/printpreview_mask.html
new file mode 100644
index 0000000000..f1ea3af255
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_mask.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<style>
+div {
+ background: black;
+ color: white;
+ -webkit-mask-image: linear-gradient(180deg,#000 60%,transparent);
+ mask-image: linear-gradient(180deg,#000 60%,transparent);
+}
+</style>
+<div>
+ Here's some text<br>
+ Here's some text<br>
+ Here's some text<br>
+ Here's some text<br>
+ Here's some text<br>
+</div>
diff --git a/layout/base/tests/chrome/printpreview_mixed_page_size_001.html b/layout/base/tests/chrome/printpreview_mixed_page_size_001.html
new file mode 100644
index 0000000000..a611299527
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_mixed_page_size_001.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+@page a {
+ size: 10in 5in;
+}
+@page b {
+ size: 6in 9in;
+}
+</style>
+<div style="page: a">a</div>
+<div style="page: b">b</div>
diff --git a/layout/base/tests/chrome/printpreview_mixed_page_size_002.html b/layout/base/tests/chrome/printpreview_mixed_page_size_002.html
new file mode 100644
index 0000000000..f55efef3e6
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_mixed_page_size_002.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<style>
+@page a {
+ size: 10in 5in;
+}
+@page b {
+ size: 6in 9in;
+}
+</style>
+<div style="display: flex; position: fixed;">
+ <div>static 1</div>
+ <div>static 2</div>
+</div>
+<div style="page: a">block a</div>
+<div style="page: b">block b</div>
diff --git a/layout/base/tests/chrome/printpreview_pps16.html b/layout/base/tests/chrome/printpreview_pps16.html
new file mode 100644
index 0000000000..fc94819340
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps16.html
@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "16-pages-per-sheet" scenario.
+ There are 16 full-page color-swatches. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+.swatch:nth-child(10) { border-color: salmon; }
+.swatch:nth-child(11) { border-color: lightgreen; }
+.swatch:nth-child(12) { border-color: navy; }
+.swatch:nth-child(13) { border-color: brown; }
+.swatch:nth-child(14) { border-color: orchid; }
+.swatch:nth-child(15) { border-color: goldenrod; }
+.swatch:nth-child(16) { border-color: seagreen; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps16_ref.html b/layout/base/tests/chrome/printpreview_pps16_ref.html
new file mode 100644
index 0000000000..5807cc44ec
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps16_ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "16-pages-per-sheet" scenario. The width
+ and height of each "swatch" is 1/4 the width and height of the page. -->
+<style>
+html, body { margin: 0; height: 100%; }
+body {
+ display: flex;
+ flex-flow: row wrap;
+}
+.swatch {
+ box-sizing: border-box;
+ border: 30px solid;
+ /* The height will be automatically set to the flex container's row height,
+ via default 'align-self' behavior (which ends up as 'stretch') */
+ flex: 1 25%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+.swatch:nth-child(10) { border-color: salmon; }
+.swatch:nth-child(11) { border-color: lightgreen; }
+.swatch:nth-child(12) { border-color: navy; }
+.swatch:nth-child(13) { border-color: brown; }
+.swatch:nth-child(14) { border-color: orchid; }
+.swatch:nth-child(15) { border-color: goldenrod; }
+.swatch:nth-child(16) { border-color: seagreen; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps2.html b/layout/base/tests/chrome/printpreview_pps2.html
new file mode 100644
index 0000000000..a7f9fddae3
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps2.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "2-pages-per-sheet" scenario.
+ There are 2 full-page "swatches" with large colorful borders. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 240px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: pink; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps2_ref.html b/layout/base/tests/chrome/printpreview_pps2_ref.html
new file mode 100644
index 0000000000..b1e8033c87
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps2_ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "2-pages-per-sheet" scenario. The width and
+ height of each "swatch" is 0.625 the width and height of the page. That
+ 0.625 scale-factor comes from the fact that, when rendering the testcase,
+ we'll need to scale the page-width (8in) down enough to fit side-by-side
+ into half of the page height (10in/2 = 5in), on a rotated sheet. So we've
+ got to scale 8in to fit into 5in, which is a scale factor of 0.625. -->
+<style>
+html { height: 100%; }
+body {
+ height: 100%;
+ margin: 0;
+ box-sizing: border-box;
+
+ /* The testcase (rendered at 2-pages-per-sheet) will have 1.75in of extra
+ space in the vertical axis, which will be distributed equally with 0.875in
+ on the top and the bottom of the page grid. We mock that up as padding
+ here: */
+ padding: 0.875in 0;
+
+ /* We lay out the body as a row-oriented flex container, with two
+ side-by-side children which correspond to the two pages per sheet: */
+ display: flex;
+}
+.swatch {
+ box-sizing: border-box;
+
+ /* This represents the 240px border in the testcase, scaled down 0.625x: */
+ border: 150px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: pink; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps4.html b/layout/base/tests/chrome/printpreview_pps4.html
new file mode 100644
index 0000000000..6e3d030c6d
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps4.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "4-pages-per-sheet" scenario.
+ There are 4 full-page "swatches" with large colorful borders. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps4_ref.html b/layout/base/tests/chrome/printpreview_pps4_ref.html
new file mode 100644
index 0000000000..4ffbec505f
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps4_ref.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "4-pages-per-sheet" scenario. The width
+ and height of each "swatch" is 1/2 the width and height of the page. -->
+<style>
+html, body { margin: 0; height: 100%; }
+body {
+ display: flex;
+ flex-flow: row wrap;
+}
+.swatch {
+ box-sizing: border-box;
+ border: 60px solid;
+ /* The height will be automatically set to the flex container's row height,
+ via default 'align-self' behavior (which ends up as 'stretch') */
+ flex: 1 50%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps6.html b/layout/base/tests/chrome/printpreview_pps6.html
new file mode 100644
index 0000000000..68722afba9
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps6.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "6-pages-per-sheet" scenario.
+ There are 12 full-page color-swatches, which should be layed out over 2
+ sheets.
+
+ This test is specifically given more pages than fit on a sheet so that
+ at least one of our tests checks the case where we have more than one
+ sheet. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+.swatch:nth-child(10) { border-color: salmon; }
+.swatch:nth-child(11) { border-color: lightgreen; }
+.swatch:nth-child(12) { border-color: navy; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps6_ref.html b/layout/base/tests/chrome/printpreview_pps6_ref.html
new file mode 100644
index 0000000000..3af174e0fa
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps6_ref.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "6-pages-per-sheet" scenario. This file is
+ a mockup of a sheet with 6 pages, followed by a sheet with another 6 pages,
+ with the pages all having a 0.4x scale factor applied. -->
+<style>
+html { height: 100%; }
+body {
+ height: 100%;
+ margin: 0;
+ box-sizing: border-box;
+}
+
+.sheet {
+ height: 100%;
+
+ /* We lay out the body as a column-oriented flex container (whose children,
+ in turn, are rows). */
+ display: flex;
+ flex-direction: column;
+}
+
+.row {
+ /* Give each row an equal share of the available height: */
+ flex: 1;
+
+ /* ...and render them as row-oriented (by default) flex containers: */
+ display: flex;
+}
+
+.swatch {
+ box-sizing: border-box;
+
+ /* This represents the 120px border in the testcase, scaled down 0.4x: */
+ border: 48px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+
+ /* The testcase (rendered at 6-pages-per-sheet) will have 0.2in of extra
+ space in the horizontal axis, which will be distributed equally with
+ 0.05in to the left and right of each page in the grid. We mock that up as
+ margin here: */
+ margin: 0 0.05in;
+}
+
+.sheet:nth-child(1) > .row:nth-child(1) > .swatch:nth-child(1) { border-color: cyan; }
+.sheet:nth-child(1) > .row:nth-child(1) > .swatch:nth-child(2) { border-color: yellow; }
+.sheet:nth-child(1) > .row:nth-child(2) > .swatch:nth-child(1) { border-color: pink; }
+.sheet:nth-child(1) > .row:nth-child(2) > .swatch:nth-child(2) { border-color: orange; }
+.sheet:nth-child(1) > .row:nth-child(3) > .swatch:nth-child(1) { border-color: purple; }
+.sheet:nth-child(1) > .row:nth-child(3) > .swatch:nth-child(2) { border-color: olive; }
+
+.sheet:nth-child(2) { break-before: always; }
+.sheet:nth-child(2) > .row:nth-child(1) > .swatch:nth-child(1) { border-color: blue; }
+.sheet:nth-child(2) > .row:nth-child(1) > .swatch:nth-child(2) { border-color: tan; }
+.sheet:nth-child(2) > .row:nth-child(2) > .swatch:nth-child(1) { border-color: fuchsia; }
+.sheet:nth-child(2) > .row:nth-child(2) > .swatch:nth-child(2) { border-color: salmon; }
+.sheet:nth-child(2) > .row:nth-child(3) > .swatch:nth-child(1) { border-color: lightgreen; }
+.sheet:nth-child(2) > .row:nth-child(3) > .swatch:nth-child(2) { border-color: navy; }
+</style>
+<div class="sheet">
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+</div>
+<div class="sheet">
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+</div>
diff --git a/layout/base/tests/chrome/printpreview_pps9.html b/layout/base/tests/chrome/printpreview_pps9.html
new file mode 100644
index 0000000000..341e5fdf81
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps9.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "9-pages-per-sheet" scenario.
+ There are 9 full-page color-swatches. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps9_ref.html b/layout/base/tests/chrome/printpreview_pps9_ref.html
new file mode 100644
index 0000000000..ab90c837aa
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps9_ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "9-pages-per-sheet" scenario. The width
+ and height of each "swatch" is 1/3 the width and height of the page. -->
+<style>
+html, body { margin: 0; height: 100%; }
+body {
+ display: flex;
+ flex-flow: row wrap;
+}
+.swatch {
+ box-sizing: border-box;
+ border: 40px solid;
+ /* The height will be automatically set to the flex container's row height,
+ via default 'align-self' behavior (which ends up as 'stretch') */
+ /* Note: it's OK that the flex-basis isn't exactly 1/3 here.
+ Flexbox layout will give each flex item 33% of the width,
+ and then divide up the remaining amount equally. This
+ results in exactly 3 items fitting per row and each one
+ getting 1/3 of a row, which is all we're going for. */
+ flex: 1 33%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw2.html b/layout/base/tests/chrome/printpreview_pps_uw2.html
new file mode 100644
index 0000000000..34d3af49de
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw2.html
@@ -0,0 +1,16 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "2-pages-per-sheet" scenario with nonzero
+ unwriteable margins. There are 2 full-page "swatches" with large colorful
+ borders. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 240px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: pink; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw2_no_margin_ref.html b/layout/base/tests/chrome/printpreview_pps_uw2_no_margin_ref.html
new file mode 100644
index 0000000000..223aab8d55
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw2_no_margin_ref.html
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "2-pages-per-sheet" scenario zero
+ unwriteable margins. We're scaling down 8in width to fit 2 in 10in,
+ so the scale factor is (10 / 2) / 8 = 0.625x. -->
+<style>
+html { height: 100%; }
+body {
+ height: 100%;
+ margin: 0;
+ box-sizing: border-box;
+
+ /* The testcase (rendered at 2-pages-per-sheet) will have 8 - (10in * 0.625)
+ * = 1.75in of extra vertical space. */
+ padding: 0.875in 0;
+
+ /* We lay out the body as a row-oriented flex container, with two
+ side-by-side children which correspond to the two pages per sheet: */
+ display: flex;
+}
+.swatch {
+ box-sizing: border-box;
+
+ /* This represents the 240px border in the testcase, scaled down 0.5x: */
+ border: 150px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+ margin: 0;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: pink; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw2_ref.html b/layout/base/tests/chrome/printpreview_pps_uw2_ref.html
new file mode 100644
index 0000000000..d923901096
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw2_ref.html
@@ -0,0 +1,49 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "2-pages-per-sheet" scenario with nonzero
+ unwriteable margins. We expect the pages to have a 0.5x scale-down
+ factor. That 0.5 scale-factor comes from the fact that, when rendering the
+ testcase, we'll need to scale the page-width (8in) down enough to fit
+ side-by-side into half of the sheet's available width (with the sheet
+ having been rotated to landscape mode), with the sheet's unwritable margin
+ having already been subtracted out. The sheet's width (in landscape mode)
+ is 10in, and its unwriteable margin in that axis is 2in, so it's got 8in
+ of available width to hold two side-by-side pages, i.e. 4in per page.
+ Since the page width was 8in, that makes for a 0.5x scale. -->
+<style>
+html { height: 100%; }
+body {
+ height: 100%;
+ margin: 0;
+ box-sizing: border-box;
+
+ /* The testcase (rendered at 2-pages-per-sheet) will have 2in of extra space
+ in the vertical axis, which will be distributed equally with 1in on the
+ top and the bottom of the page grid (separately from the sheet's
+ unwriteable margin). We mock that up as padding here: */
+ padding: 1in 0;
+
+ /* We lay out the body as a row-oriented flex container, with two
+ side-by-side children which correspond to the two pages per sheet: */
+ display: flex;
+}
+.swatch {
+ box-sizing: border-box;
+
+ /* This represents the 240px border in the testcase, scaled down 0.5x: */
+ border: 120px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+
+ /* This margin is meant to mock up the unwriteable margin for each page on
+ our sheet; it's exactly 0.5x the unwriteableMargin values specified for
+ the testcase in printpreview_helper.xhtml. */
+ margin: 0.4in 0.3in 0.6in 0.2in;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: pink; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw4.html b/layout/base/tests/chrome/printpreview_pps_uw4.html
new file mode 100644
index 0000000000..6e3d030c6d
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw4.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "4-pages-per-sheet" scenario.
+ There are 4 full-page "swatches" with large colorful borders. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw4_ref.html b/layout/base/tests/chrome/printpreview_pps_uw4_ref.html
new file mode 100644
index 0000000000..a603ff2be9
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw4_ref.html
@@ -0,0 +1,70 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "4-pages-per-sheet" scenario. This file is
+ a mockup of a sheet with 4 pages, with the pages all having a 0.4x scale
+ scale factor applied. (We end up with that scale factor by subtracting the
+ requested unwriteable margins from the sheet, and dividing up the
+ remaining space equally among the "virtual pages".) -->
+<style>
+html {
+ display: flex;
+ height: 100%;
+ margin: 0;
+}
+body {
+ /* As a flex item (a child of the html element), fill the available area. */
+ flex: 1;
+
+ /* We lay out the body as a column-oriented flex container (whose children,
+ in turn, are rows). */
+ display: flex;
+ flex-direction: column;
+
+ /* These values come directly from the unwriteableMargin values in the
+ testcase's configuration code in printpreview_helper.xhtml. */
+ margin-top: 0.6in;
+ margin-right: 0.1in;
+ margin-bottom: 0.4in;
+ margin-left: 0.3in;
+}
+
+.row {
+ /* Give each row an equal share of the available height: */
+ flex: 1;
+
+ /* ...and render them as row-oriented (by default) flex containers: */
+ display: flex;
+}
+
+.swatch {
+ box-sizing: border-box;
+
+ /* These represent the 120px borders in the testcase, scaled down 0.4x: */
+ border: 48px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+
+ /* These values come directly from the unwriteableMargin values in the
+ testcase's configuration code in printpreview_helper.xhtml, with
+ each measurement scaled down by exactly 0.4x. The extra 0.1in accounts
+ for the centering of each page in its grid cell. */
+ margin-top: 0.24in;
+ margin-right: calc(0.04in + 0.1in);
+ margin-bottom: 0.16in;
+ margin-left: calc(0.12in + 0.1in);
+}
+.row:nth-child(1) > .swatch:nth-child(1) { border-color: cyan; }
+.row:nth-child(1) > .swatch:nth-child(2) { border-color: yellow; }
+.row:nth-child(2) > .swatch:nth-child(1) { border-color: pink; }
+.row:nth-child(2) > .swatch:nth-child(2) { border-color: orange; }
+</style>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw9.html b/layout/base/tests/chrome/printpreview_pps_uw9.html
new file mode 100644
index 0000000000..9bd0e1f5a4
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw9.html
@@ -0,0 +1,29 @@
+<!DOCTYPE html>
+<!-- This is a testcase for a "9-pages-per-sheet" scenario.
+ There are 9 full-page "swatches" with large colorful borders. -->
+<style>
+html, body { margin: 0; height: 100%; }
+.swatch {
+ box-sizing: border-box;
+ border: 120px solid;
+ height: 100%;
+}
+.swatch:nth-child(1) { border-color: cyan; }
+.swatch:nth-child(2) { border-color: yellow; }
+.swatch:nth-child(3) { border-color: pink; }
+.swatch:nth-child(4) { border-color: orange; }
+.swatch:nth-child(5) { border-color: purple; }
+.swatch:nth-child(6) { border-color: olive; }
+.swatch:nth-child(7) { border-color: blue; }
+.swatch:nth-child(8) { border-color: tan; }
+.swatch:nth-child(9) { border-color: fuchsia; }
+</style>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
+<div class="swatch"></div>
diff --git a/layout/base/tests/chrome/printpreview_pps_uw9_ref.html b/layout/base/tests/chrome/printpreview_pps_uw9_ref.html
new file mode 100644
index 0000000000..de3cb1e08b
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_pps_uw9_ref.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<!-- This is a reference case for a "9-pages-per-sheet" scenario. This file is
+ a mockup of a sheet with 9 pages, with the pages all having a 0.2x scale
+ scale factor applied. (We end up with that scale factor by subtracting the
+ requested unwriteable margins from the sheet, and dividing up the
+ remaining space equally among the "virtual pages".) -->
+<style>
+html {
+ display: flex;
+ height: 100%;
+ margin: 0;
+}
+body {
+ /* As a flex item (a child of the html element), fill the available area. */
+ flex: 1;
+
+ /* We lay out the body as a column-oriented flex container (whose children,
+ in turn, are rows). */
+ display: flex;
+ flex-direction: column;
+
+ /* These values come directly from the unwriteableMargin values in the
+ testcase's configuration code in printpreview_helper.xhtml. */
+ margin-top: 0.2in;
+ margin-right: 0.8in;
+ margin-bottom: 0.4in;
+ margin-left: 1.2in;
+}
+
+.row {
+ /* Give each row an equal share of the available height: */
+ flex: 1;
+
+ /* ...and render them as row-oriented (by default) flex containers: */
+ display: flex;
+}
+
+.swatch {
+ box-sizing: border-box;
+
+ /* These represent the 120px borders in the testcase, scaled down 0.2x: */
+ border: 24px solid;
+
+ /* Share the width equally among the swatches. (The height will be
+ automatically set to the flex container's row height, via default
+ 'align-self' behavior.) */
+ flex: 1;
+
+ /* These values come directly from the unwriteableMargin values in the
+ testcase's configuration code in printpreview_helper.xhtml, with
+ each measurement scaled down by exactly 0.2x. The extra 0.5667in accounts
+ for the centering of each page in its grid cell. */
+ margin-top: calc(0.04in + 0.5667in);
+ margin-right: 0.16in;
+ margin-bottom: calc(0.08in + 0.5667in);
+ margin-left: 0.24in;
+}
+.row:nth-child(1) > .swatch:nth-child(1) { border-color: cyan; }
+.row:nth-child(1) > .swatch:nth-child(2) { border-color: yellow; }
+.row:nth-child(1) > .swatch:nth-child(3) { border-color: pink; }
+.row:nth-child(2) > .swatch:nth-child(1) { border-color: orange; }
+.row:nth-child(2) > .swatch:nth-child(2) { border-color: purple; }
+.row:nth-child(2) > .swatch:nth-child(3) { border-color: olive; }
+.row:nth-child(3) > .swatch:nth-child(1) { border-color: blue; }
+.row:nth-child(3) > .swatch:nth-child(2) { border-color: tan; }
+.row:nth-child(3) > .swatch:nth-child(3) { border-color: fuchsia; }
+</style>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
+<div class="row">
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+ <div class="swatch"></div>
+</div>
diff --git a/layout/base/tests/chrome/printpreview_prettyprint.xml b/layout/base/tests/chrome/printpreview_prettyprint.xml
new file mode 100644
index 0000000000..759d5066cf
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_prettyprint.xml
@@ -0,0 +1 @@
+<out>Here be sea hags</out>
diff --git a/layout/base/tests/chrome/printpreview_prettyprint_ref.xhtml b/layout/base/tests/chrome/printpreview_prettyprint_ref.xhtml
new file mode 100644
index 0000000000..7309425fb4
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_prettyprint_ref.xhtml
@@ -0,0 +1,3 @@
+<out><div id="top" xmlns="http://www.w3.org/1999/xhtml"><link href="chrome://global/content/xml/XMLPrettyPrint.css" type="text/css" rel="stylesheet"/><div id="header"><p>
+ This XML file does not appear to have any style information associated with it. The document tree is shown below.
+ </p></div><main id="tree" class="highlight"><div>&lt;<span class="start-tag">out</span>&gt;<span class="text">Here be sea hags</span>&lt;/<span class="end-tag">out</span>&gt;</div></main></div></out>
diff --git a/layout/base/tests/chrome/printpreview_quirks.html b/layout/base/tests/chrome/printpreview_quirks.html
new file mode 100644
index 0000000000..fa8714a0f7
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_quirks.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset="utf-8">
+<style>
+ .HiDdEn { display: none }
+</style>
+<body class="hidden">
+ Some content that I should be able to print.
+</body>
diff --git a/layout/base/tests/chrome/printpreview_quirks_ref.html b/layout/base/tests/chrome/printpreview_quirks_ref.html
new file mode 100644
index 0000000000..4c6fcce1f6
--- /dev/null
+++ b/layout/base/tests/chrome/printpreview_quirks_ref.html
@@ -0,0 +1,5 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+ Some content that I should be able to print.
+</body>
diff --git a/layout/base/tests/chrome/red.png b/layout/base/tests/chrome/red.png
new file mode 100644
index 0000000000..57bf3ddc52
--- /dev/null
+++ b/layout/base/tests/chrome/red.png
Binary files differ
diff --git a/layout/base/tests/chrome/test_bug1018265.xhtml b/layout/base/tests/chrome/test_bug1018265.xhtml
new file mode 100644
index 0000000000..8e8ac9a119
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug1018265.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1018265
+-->
+<window title="Mozilla Bug 1018265"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="run()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+
+ /** Test for Bug 1018265 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ window.openDialog("file_bug1018265.xhtml", "documentViewerTest", "chrome,width=100,height=100,noopener", window);
+ }
+
+ function done() {
+ ok(true, "done");
+ setTimeout(function() { SimpleTest.finish(); }, 0);
+ }
+ ]]>
+ </script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1018265"
+ target="_blank">Mozilla Bug 1018265</a>
+ </body>
+</window>
diff --git a/layout/base/tests/chrome/test_bug1041200.xhtml b/layout/base/tests/chrome/test_bug1041200.xhtml
new file mode 100644
index 0000000000..365ecf2825
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug1041200.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?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">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ window.openDialog("bug1041200_window.html", "bug1041200",
+ "chrome,width=800,height=800,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug396367-1.html b/layout/base/tests/chrome/test_bug396367-1.html
new file mode 100644
index 0000000000..63b33d335d
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug396367-1.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396367
+-->
+<head>
+ <title>Test for Bug 396367</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function finish() {
+ ok(true, "didn't crash");
+ top.docShell.browsingContext.textZoom = 1;
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396367">Mozilla Bug 396367</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+
+<input>
+<script>document.body.setAttribute('style', 'display: -moz-box; overflow: scroll;');</script>
+<script>
+top.docShell.browsingContext.textZoom = Math.floor(10 * Math.random()) / 4 + 0.2;
+document.documentElement.offsetHeight;
+setTimeout(finish, 0);
+</script>
+
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug396367-2.html b/layout/base/tests/chrome/test_bug396367-2.html
new file mode 100644
index 0000000000..2a751cd8be
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug396367-2.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396367
+-->
+<head>
+ <title>Test for Bug 396367</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <style>select::after { content:"m"; }</style>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function finish() {
+ ok(true, "didn't crash");
+ top.docShell.browsingContext.textZoom = 1;
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=396367">Mozilla Bug 396367</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+
+<div style="overflow: scroll; float: left;">
+
+<select></select>
+
+<li style="display: table-cell;">
+
+<script>
+top.docShell.browsingContext.textZoom = Math.floor(10 * Math.random()) / 4 + 0.2;
+document.documentElement.offsetHeight;
+setTimeout(finish, 0);
+</script>
+</li>
+</div>
+
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug420499.xhtml b/layout/base/tests/chrome/test_bug420499.xhtml
new file mode 100644
index 0000000000..22fefd7987
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug420499.xhtml
@@ -0,0 +1,126 @@
+<?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=420499
+-->
+<window title="Mozilla Bug 420499" onload="setTimeout(focusInput, 500);"
+ 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>
+
+
+
+ <menu id="menu" label="Menu">
+ <menupopup id="file-popup">
+ <!-- <input xmlns="http://www.w3.org/1999/xhtml" id="some-text" maxlength="10" value="some text"/> -->
+ <menu label="submenu">
+ <menupopup id="file-popup-inner">
+
+ <menuitem label="Item1"/>
+ <menuitem label="Item2"/>
+ <input xmlns="http://www.w3.org/1999/xhtml" id="some-text" maxlength="10" value="some more text"/>
+ </menupopup>
+ </menu>
+ <menuitem label="Item3"/>
+ <menuitem label="Item4"/>
+ </menupopup>
+ </menu>
+
+ <popupset>
+ <menupopup id="contextmenu">
+ <menuitem label="Cut"/>
+ <menuitem label="Copy"/>
+ <menuitem label="Paste"/>
+ </menupopup>
+ <tooltip id="tooltip" orient="vertical">
+ <description value="This is a tooltip"/>
+ </tooltip>
+ </popupset>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml" bgcolor="white">
+
+ <p id="par1">Paragraph 1</p>
+ <p id="par2">Paragraph 2</p>
+ <p id="par3">Paragraph 3</p>
+ <p id="par4">Paragraph 4</p>
+ <p id="par5">Paragraph 5</p>
+
+ <input type="text" id="text-input" maxlength="10" value="some more text"/> <br />
+
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=420499"
+ target="_blank">Mozilla Bug 420499</a>
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript"><![CDATA[
+
+ /** Test for Bug 420499 **/
+ SimpleTest.waitForExplicitFinish();
+
+ function getSelectionController() {
+ return window.docShell
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController);
+ }
+
+ function isCaretVisible() {
+ var docShell = window.docShell;
+ var selCon = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsISelectionDisplay)
+ .QueryInterface(Ci.nsISelectionController);
+ return selCon.caretVisible;
+ }
+
+ function focusInput() {
+ ok(!isCaretVisible(), "Caret shouldn't be visible");
+ $("text-input").focus();
+ ok(isCaretVisible(), "Caret should be visible when input focused");
+ window.addEventListener("popupshown", popupMenuShownHandler);
+ $("menu").open = true;
+ }
+
+ function popupMenuShownHandler() {
+ window.removeEventListener("popupshown", popupMenuShownHandler);
+ ok(!isCaretVisible(), "Caret shouldn't be visible when menu open");
+ window.addEventListener("popuphidden", ensureParagraphFocused);
+ $("menu").open = false;
+ }
+
+ function ensureParagraphFocused() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused);
+ ok(isCaretVisible(), "Caret should have returned to previous focus");
+ window.addEventListener("popupshown", popupMenuShownHandler2);
+ $("contextmenu").openPopup($('text-input'), "topleft" , -1 , -1 , true, true);
+ }
+
+ function popupMenuShownHandler2() {
+ window.removeEventListener("popupshown", popupMenuShownHandler2);
+ ok(isCaretVisible(), "Caret should be visible when context menu open");
+ window.addEventListener("popuphidden", ensureParagraphFocused2);
+ document.getElementById("contextmenu").hidePopup();
+ }
+
+ function ensureParagraphFocused2() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused2);
+ ok(isCaretVisible(), "Caret should still be visible");
+ window.addEventListener("popupshown", tooltipShownHandler);
+ $("tooltip").openPopup($('text-input'), "topleft" , -1 , -1 , false, true);
+ }
+
+ function tooltipShownHandler() {
+ window.removeEventListener("popupshown", tooltipShownHandler);
+ ok(isCaretVisible(), "Caret should be visible when tooltip is visible");
+ window.addEventListener("popuphidden", ensureParagraphFocused3);
+ document.getElementById("tooltip").hidePopup();
+ }
+
+ function ensureParagraphFocused3() {
+ window.removeEventListener("popuphidden", ensureParagraphFocused2);
+ ok(isCaretVisible(), "Caret should still be visible");
+ SimpleTest.finish();
+ }
+ ]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug458898.html b/layout/base/tests/chrome/test_bug458898.html
new file mode 100644
index 0000000000..a7913f9a2a
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug458898.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=458898
+-->
+<head>
+ <title>Test for Bug 458898</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=458898">Mozilla Bug 458898</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+var win = window.browsingContext.topChromeWindow.openDialog("file_bug458898.html");
+
+function loaded() {
+ var disableWindowResizePref = "dom.disable_window_move_resize";
+ SpecialPowers.pushPrefEnv({"set":[[disableWindowResizePref, false]]}, function() {
+ win.sizeToContent();
+ ok(win.innerWidth >= 100, "innerWidth: " + win.innerWidth + " >= 100 ?");
+ ok(win.innerHeight >= 200, "innerHeight: " + win.innerHeight + " >= 200 ?");
+ win.close();
+ SimpleTest.finish();
+ });
+}
+
+win.addEventListener("load", loaded);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug465448.xhtml b/layout/base/tests/chrome/test_bug465448.xhtml
new file mode 100644
index 0000000000..fa9d1589a2
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug465448.xhtml
@@ -0,0 +1,45 @@
+<?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 title="Bug 465448"
+ onload="loaded()"
+ 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>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+
+<script><![CDATA[
+SimpleTest.waitForExplicitFinish();
+var loadedCalled = false;
+var win = window.open("file_bug465448.html", "_blank", "width=600,height=600");
+
+function loaded() {
+ if (!loadedCalled) {
+ loadedCalled = true;
+ return;
+ }
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ win.sizeToContent();
+ ok(win.innerWidth >= 100, "innerWidth");
+ ok(win.innerHeight >= 200, "innerHeight");
+ win.close();
+ SimpleTest.finish();
+}
+]]></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+</p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+
+</window>
diff --git a/layout/base/tests/chrome/test_bug514660.xhtml b/layout/base/tests/chrome/test_bug514660.xhtml
new file mode 100644
index 0000000000..367eec8bf4
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug514660.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514660
+-->
+<window title="Mozilla Bug 504311"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=514660"
+ target="_blank">Mozilla Bug 514660</a>
+<textarea></textarea>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function doTest()
+{
+ var viewer = window.docShell.docViewer;
+ viewer.authorStyleDisabled = true;
+
+ document.documentElement.getBoundingClientRect();
+ ok(true, "Didn't crash");
+
+ viewer.authorStyleDisabled = false;
+
+ SimpleTest.finish();
+}
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug533845.xhtml b/layout/base/tests/chrome/test_bug533845.xhtml
new file mode 100644
index 0000000000..3ea21cb9b7
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug533845.xhtml
@@ -0,0 +1,49 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=533845
+-->
+<window title="Mozilla Bug 533845"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<panel id="panel" style="width: 500px; height: 500px">
+ <iframe type="content" id="contentFrame" src="data:text/html,&lt;html&gt;&lt;body onclick='document.body.textContent=1'&gt;This is a panel!&lt;/body&gt;&lt;/html&gt;" width="500" height="500"/>
+</panel>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=533845"
+ target="_blank">Mozilla Bug 533845</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function doTest() {
+ let panel = document.getElementById("panel");
+ panel.addEventListener("popupshown", function onpopupshown() {
+ continueTest();
+ panel.addEventListener("popuphidden", function onpopuphidden() {
+ SimpleTest.finish();
+ }, { once: true });
+ panel.hidePopup();
+ }, { once: true });
+ panel.openPopup();
+}
+
+function continueTest() {
+ var ifrwindow = document.getElementById("contentFrame").contentWindow;
+ ifrwindow.focus();
+ var utils = ifrwindow.windowUtils;
+ var rect = ifrwindow.document.body.getBoundingClientRect();
+ var x = rect.left + (rect.width/2);
+ var y = rect.top + (rect.height/2);
+ utils.sendMouseEvent("mousedown", x, y, 0, 1, 0);
+ utils.sendMouseEvent("mouseup", x, y, 0, 1, 0);
+ is(ifrwindow.document.body.textContent, "1", "Should have got a click event!");
+}
+
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_bug551434.html b/layout/base/tests/chrome/test_bug551434.html
new file mode 100644
index 0000000000..5e21cf6408
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug551434.html
@@ -0,0 +1,95 @@
+<html>
+<head>
+ <title>Test for Bug 551434</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+</div>
+<pre id="test">
+<input id="i1" onkeydown="gKeyDown1++; $('i2').focus();" onkeypress="gKeyPress1++;" onkeyup="gKeyUp1++;"/>
+<input id="i2" onkeydown="gKeyDown2++;" onkeypress="gKeyPress2++;" onkeyup="gKeyUp2++;"/>
+
+<input id="i3" onkeydown="gKeyDown3++; frames[0].document.getElementById('i4').focus();"
+ onkeypress="gKeyPress3++;" onkeyup="gKeyUp3++;"/>
+<iframe id="iframe" src="http://example.org/chrome/layout/base/tests/chrome/bug551434_childframe.html"></iframe>
+
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var gKeyDown1 = 0, gKeyPress1 = 0, gKeyUp1 = 0;
+var gKeyDown2 = 0, gKeyPress2 = 0, gKeyUp2 = 0;
+var gKeyDown3 = 0, gKeyPress3 = 0, gKeyUp3 = 0;
+
+function runTest()
+{
+ $("i1").focus();
+
+ // key events should not be retargeted when the focus changes to an
+ // element in the same document.
+ synthesizeKey("a", {type: "keydown"});
+ is(document.activeElement, $("i2"), "input 2 in focused");
+
+ synthesizeKey("a", {type: "keyup"});
+
+ is(gKeyDown1, 1, "keydown on input 1");
+ is(gKeyPress1, 0, "keypress on input 1");
+ is(gKeyUp1, 0, "keyup on input 1");
+ is(gKeyDown2, 0, "keydown on input 2");
+ is(gKeyPress2, 1, "keypress on input 2");
+ is(gKeyUp2, 1, "keyup on input 2");
+
+ is($("i1").value, "", "input 1 value");
+ is($("i2").value, "a", "input 2 value");
+
+ // key events should however be retargeted when the focus changes to an
+ // element in the a content document from a chrome document.
+ $("i3").focus();
+
+ var childWinObj = frames[0].wrappedJSObject;
+
+ sendString("b");
+ is(gKeyDown3, 1, "keydown on input 3");
+ is(gKeyPress3, 1, "keypress on input 3");
+ is(gKeyUp3, 1, "keyup on input 3");
+ is(childWinObj.gKeyDownChild, 0, "keydown on input 4");
+ is(childWinObj.gKeyPressChild, 0, "keypress on input 4");
+ is(childWinObj.gKeyUpChild, 0, "keyup on input 4");
+
+ var i4 = frames[0].document.getElementById("i4");
+ is($("i3").value, "b", "input 3 value");
+ is(i4.value, "", "input 4 value");
+
+ is(document.activeElement, $("iframe"), "parent focus");
+ is(frames[0].document.activeElement, i4, "child focus");
+
+ // key events should also be retargeted when the focus changes to an
+ // element in a chrome document from a content document.
+ i4.addEventListener("keydown", () => $("i3").focus());
+
+ sendString("c");
+
+ is(gKeyDown3, 1, "keydown on input 3");
+ is(gKeyPress3, 1, "keypress on input 3");
+ is(gKeyUp3, 1, "keyup on input 3");
+ is(childWinObj.gKeyDownChild, 1, "keydown on input 4");
+ is(childWinObj.gKeyPressChild, 1, "keypress on input 4");
+ is(childWinObj.gKeyUpChild, 1, "keyup on input 4");
+
+ is($("i3").value, "b", "input 3 value");
+ is(i4.value, "c", "input 4 value");
+
+ is(document.activeElement, $("i3"), "parent focus");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/chrome/test_bug708062.html b/layout/base/tests/chrome/test_bug708062.html
new file mode 100644
index 0000000000..ee7df7d37d
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug708062.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=708062
+-->
+<head>
+ <title>Test for Bug 708062</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="doTest()">
+
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=708062">Mozilla Bug 708062</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<iframe id="f" style="width:100px;"
+ src="data:text/html,A<div id='d' style='position:fixed;width:170px;top:0;right:0;height:1px;background:yellow;'>"></iframe>
+<pre id="test">
+
+<script>
+function isBoundingClientRect(e, r, msg) {
+ var BCR = e.getBoundingClientRect();
+ is([BCR.left, BCR.top, BCR.right, BCR.bottom].join(','), r, msg);
+}
+
+function doTest() {
+ var f = document.getElementById('f');
+
+ var d = f.contentDocument.getElementById('d');
+
+ isBoundingClientRect(d, "-70,0,100,1", "initial rect");
+ SpecialPowers.setFullZoom(f.contentWindow, 2);
+ isBoundingClientRect(d, "-120,0,50,1", "after zooming in");
+ SpecialPowers.setFullZoom(f.contentWindow, 1);
+ isBoundingClientRect(d, "-70,0,100,1", "after zooming back out");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_bug812817.xhtml b/layout/base/tests/chrome/test_bug812817.xhtml
new file mode 100644
index 0000000000..c5eb747ea6
--- /dev/null
+++ b/layout/base/tests/chrome/test_bug812817.xhtml
@@ -0,0 +1,37 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=812817
+-->
+<window title="Mozilla Bug 812817"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ onload="doTest()">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<panel id="panel" width="200" height="200" onpopupshown="continueTest()">
+</panel>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=812817"
+ target="_blank">Mozilla Bug 812817</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+var panel = document.getElementById('panel');
+function doTest() {
+ panel.openPopup(null, '', 500, 500, false, false, null);
+}
+
+function continueTest() {
+ panel.style.background = "url(blue-32x32.png)";
+ setTimeout(function() {
+ ok(true, "Didn't crash");
+ SimpleTest.finish();
+ }, 50);
+}
+
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_chrome_content_integration.xhtml b/layout/base/tests/chrome/test_chrome_content_integration.xhtml
new file mode 100644
index 0000000000..6e6be4761a
--- /dev/null
+++ b/layout/base/tests/chrome/test_chrome_content_integration.xhtml
@@ -0,0 +1,24 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?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">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ var root = getRootDirectory(window.location.href);
+ window.openDialog(root + "chrome_content_integration_window.xhtml", "chrome_content_integration",
+ "chrome,width=200,height=300,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_color_scheme_browser.xhtml b/layout/base/tests/chrome/test_color_scheme_browser.xhtml
new file mode 100644
index 0000000000..163f83b38f
--- /dev/null
+++ b/layout/base/tests/chrome/test_color_scheme_browser.xhtml
@@ -0,0 +1,145 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+ <style>
+ #light { color-scheme: light }
+ #dark { color-scheme: dark }
+ </style>
+ </head>
+ <body>
+ <div id="dynamic-test">
+ <xul:browser type="content" remote="true" nodefaultsrc="true" class="remote" />
+ <xul:browser type="content" src="about:blank" class="nonremote" />
+ </div>
+ <div id="light">
+ <xul:browser type="content" remote="true" nodefaultsrc="true" class="remote" />
+ <xul:browser type="content" src="about:blank" class="nonremote" />
+ </div>
+ <div id="dark">
+ <xul:browser type="content" remote="true" nodefaultsrc="true" class="remote" />
+ <xul:browser type="content" src="about:blank" class="nonremote" />
+ </div>
+ <script><![CDATA[
+ SimpleTest.requestCompleteLog(); // to help diagnose intermittent bug 1787008
+
+ // FIXME: This shouldn't be needed if remote browsers would block the load event.
+ add_task(async function ensureBrowsersLoaded() {
+ info("Entering ensureBrowsersLoaded callback");
+ const triggeringPrincipal = document.nodePrincipal;
+ for (let b of document.querySelectorAll("browser[remote=true]")) {
+ let listener;
+ let loaded = new Promise(resolve => {
+ info("Entering 'loaded' callback; about to add progress listener");
+ listener = {
+ onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ info(`Got state change for ${b.parentNode.id}: ${aStateFlags}, ${aStatus}`);
+ if (
+ aStateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW &&
+ aStateFlags & Ci.nsIWebProgressListener.STATE_STOP
+ ) {
+ resolve();
+ b.removeProgressListener(this);
+ }
+ },
+ // Note: the following "onFoo" callbacks are only here for
+ // diagnostic purposes, and otherwise aren't relevant to the test.
+ onProgressChange(aWebProgress, aRequest,
+ aCurSelfProgress, aMaxSelfProgress,
+ aCurTotalProgress, aMaxTotalProgress) {
+ info(`Got progress change for ${b.parentNode.id}: ` +
+ `${aCurSelfProgress}/${aMaxSelfProgress}, ` +
+ `${aCurTotalProgress}/${aMaxTotalProgress}`);
+ },
+ onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+ info(`Got location change for ${b.parentNode.id}: ${aLocation.spec}, ${aFlags}`);
+ },
+ onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
+ info(`Got status change for ${b.parentNode.id}: ${aStatus}, ${aMessage}`);
+ },
+ onSecurityChange(aWebProgress, aRequest, aState) {
+ info(`Got security change for ${b.parentNode.id}: ${aState}`);
+ },
+ onContentBlockingEvent(aWebProgress, aRequest, aEvent) {
+ info(`Got content blocking event for ${b.parentNode.id}: ${aEvent}`);
+ },
+ QueryInterface: ChromeUtils.generateQI([
+ "nsIWebProgressListener",
+ "nsISupportsWeakReference",
+ ]),
+ };
+ b.addProgressListener(listener);
+ });
+ info(`Calling b.loadURI for ${b.parentNode.id}`);
+ // Keep the listener alive, since it's a weak ref otherwise.
+ window.ALIVE_LISTENER = listener;
+ b.loadURI(null /*blank*/, { triggeringPrincipal });
+ await loaded;
+ delete window.ALIVE_LISTENER;
+ }
+ });
+ async function getBrowserColorScheme(browser) {
+ return SpecialPowers.spawn(browser, [], () => {
+ return content.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+ });
+ }
+ async function tick() {
+ return new Promise(resolve => {
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
+ });
+ }
+ async function testElement(id, expectedIfTop, expectedIfNonTop) {
+ let element = document.getElementById(id);
+ for (let browser of element.querySelectorAll("browser")) {
+ let scheme = await getBrowserColorScheme(browser);
+ let expected = browser.browsingContext.top == browser.browsingContext ? expectedIfTop : expectedIfNonTop;
+ is(scheme, expected, `${id}: ${browser.className} should be ${expected}`);
+ }
+ }
+ add_task(async function test_browser_color_scheme() {
+ let current = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+ for (let id of ["dynamic-test", "light", "dark"]) {
+ let expected = id == "dynamic-test" ? current : id;
+ await testElement(id, expected, expected);
+ }
+ });
+
+ add_task(async function test_browser_color_scheme_dynamic_style() {
+ let current = matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light";
+ let dynamicTest = document.getElementById("dynamic-test");
+ for (let value of ["light", "dark"]) {
+ await tick();
+ dynamicTest.style.colorScheme = value;
+ await testElement("dynamic-test", value, value);
+ }
+ dynamicTest.style.colorScheme = "";
+ await tick();
+ });
+
+ add_task(async function test_browser_color_scheme_dynamic_system() {
+ const ACTIVE_THEME_ID = Services.prefs.getCharPref("extensions.activeThemeID");
+ const DEFAULT_THEME_ID = "default-theme@mozilla.org";
+ if (ACTIVE_THEME_ID != DEFAULT_THEME_ID) {
+ info(`skipping test_browser_color_scheme_dynamic_system because the active theme is ${ACTIVE_THEME_ID} instead of ${DEFAULT_THEME_ID}`);
+ return;
+ }
+ for (let dark of [true, false]) {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["layout.css.prefers-color-scheme.content-override", 2],
+ ["ui.systemUsesDarkTheme", dark ? 1 : 0],
+ ]
+ });
+ await tick();
+ let expected = dark ? "dark" : "light";
+ await testElement("dynamic-test", expected, expected);
+ await SpecialPowers.popPrefEnv();
+ }
+ });
+ ]]></script>
+ </body>
+</html>
+
diff --git a/layout/base/tests/chrome/test_css_visibility_propagation.xhtml b/layout/base/tests/chrome/test_css_visibility_propagation.xhtml
new file mode 100644
index 0000000000..f11db96a89
--- /dev/null
+++ b/layout/base/tests/chrome/test_css_visibility_propagation.xhtml
@@ -0,0 +1,209 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+<body xmlns="http://www.w3.org/1999/xhtml"></body>
+<script>
+<![CDATA[
+const baseURL = "chrome://mochitests/content/chrome/layout/base/tests/chrome/";
+
+function checkHiddenEmbeddederState(window1, window2, expected1, expected2)
+{
+ ok(!window1.browsingContext.isUnderHiddenEmbedderElement, "window1 visible state");
+ ok(!window2.browsingContext.isUnderHiddenEmbedderElement, "window2 visible state");
+ is(window1.document.querySelector("browser").contentWindow.browsingContext.isUnderHiddenEmbedderElement, !expected1,
+ "window1 child visible state");
+ is(window2.document.querySelector("browser").contentWindow.browsingContext.isUnderHiddenEmbedderElement, !expected2,
+ "window2 child visible state");
+}
+
+// Tests that browser visibility is updated when it's swapped.
+add_task(async () => {
+ // Open two new windows to swap iframes.
+ const window1 = window.browsingContext.topChromeWindow.open(
+ baseURL + "window_css_visibility_propagation-1.xhtml",
+ "_blank", "chrome");
+ const window2 = window.browsingContext.topChromeWindow.open(
+ baseURL + "window_css_visibility_propagation-2.xhtml",
+ "_blank", "chrome");
+
+ const loadWindow1 =
+ new Promise(resolve => window1.addEventListener("load", resolve));
+ const loadWindow2 =
+ new Promise(resolve => window2.addEventListener("load", resolve));
+
+ await Promise.all([ loadWindow1, loadWindow2 ]);
+
+ checkHiddenEmbeddederState(window1, window2, false, true);
+
+ // Hide the parent of browser2.
+ let parent = window2.document.getElementById("parent");
+ parent.style.visibility = "hidden";
+ parent.getBoundingClientRect();
+
+ checkHiddenEmbeddederState(window1, window2, false, false);
+
+ const browser2 = window2.document.querySelector("browser");
+ let target = browser2.contentDocument.getElementById("button");
+ target.focus();
+
+ // browser2 is now in a visibility:hidden element in window2,
+ // so that Element.focus() shouldn't work.
+ isnot(browser2.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible browser");
+
+ // Make the parent visible.
+ parent.style.visibility = "";
+ parent.getBoundingClientRect();
+
+ checkHiddenEmbeddederState(window1, window2, false, true);
+
+ target.focus();
+
+ // browser2 is visible now, so focus() should work.
+ is(browser2.contentDocument.activeElement, target,
+ "Element.focus() should work in visible browser");
+
+ target.blur();
+ isnot(browser2.contentDocument.activeElement, target,
+ "The target element shouldn't be activeElement");
+
+ // Swap the content in browser1 for the content in browser2.
+ const browser1 = window1.document.querySelector("browser");
+ browser1.swapFrameLoaders(browser2);
+ await new Promise(resolve => setTimeout(resolve, 0));
+
+ target = browser1.contentDocument.getElementById("button");
+ target.focus();
+
+ // browser1 is in a visibility:hidden element in window1,
+ // so that Element.focus() shouldn't work.
+ isnot(browser1.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible browser");
+
+ checkHiddenEmbeddederState(window1, window2, false, true);
+
+ parent = window1.document.getElementById("parent");
+ parent.style.visibility = "visible";
+ parent.getBoundingClientRect();
+
+ checkHiddenEmbeddederState(window1, window2, true, true);
+
+ target.focus();
+
+ // Now browser1 is in a visibility:visible element, so that
+ // Element.focus() should just work.
+ is(browser1.contentDocument.activeElement, target,
+ "Element.focus() should work in visible browser");
+
+ window1.close();
+ window2.close();
+});
+
+// Tests that ancestor's visibility change doesn't clobber child
+// iframe's visibility if the child iframe is hidden by an
+// element in the ancestor document.
+add_task(async () => {
+ const tabReady = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if (event.data == "ready") {
+ resolve();
+ }
+ }, { once: true });
+ });
+ const tabWindow =
+ window.open(baseURL + "window_css_visibility_propagation-3.html");
+ await tabReady;
+
+ const childIFrame = tabWindow.document.querySelector("iframe");
+
+ const grandChildBrowser =
+ childIFrame.contentDocument.querySelector("browser");
+ let target = grandChildBrowser.contentDocument.getElementById("button");
+ target.focus();
+
+ ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
+ ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
+ ok(!grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is visible");
+
+ is(grandChildBrowser.contentDocument.activeElement, target,
+ "Element.focus() should work in visible browser");
+ target.blur();
+
+ // Hide the parent element of the grand child browser.
+ let parent = childIFrame.contentDocument.getElementById("parent");
+ parent.style.visibility = "hidden";
+ parent.getBoundingClientRect();
+
+ ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
+ ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
+ ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
+
+ target.focus();
+
+ isnot(grandChildBrowser.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible browser");
+
+ // Hide the parent element of the child iframe.
+ parent = tabWindow.document.getElementById("parent");
+ parent.style.visibility = "hidden";
+ parent.getBoundingClientRect();
+
+ target.focus();
+
+ ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
+ ok(childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is not visible");
+ ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
+
+ isnot(grandChildBrowser.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible iframe");
+
+ // Make the parent element of the child iframe visible.
+ parent.style.visibility = "visible";
+ parent.getBoundingClientRect();
+
+ ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
+ ok(!childIFrame.browsingContext.isUnderHiddenEmbedderElement, "iframe is visible");
+ ok(grandChildBrowser.browsingContext.isUnderHiddenEmbedderElement, "grandchild is not visible");
+
+ target.focus();
+
+ // Even if the child iframe is visible, but still the grand child is
+ // hidden by the parent element of the grand child browser so that
+ // we can't focus to the element in the grand child browser.
+ isnot(grandChildBrowser.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible browser");
+
+ tabWindow.close();
+});
+
+// Tests that an iframe is initially hidden by a visibility:hidden element in
+// the parent document.
+add_task(async () => {
+ const tabReady = new Promise(resolve => {
+ window.addEventListener("message", event => {
+ if (event.data == "ready") {
+ resolve();
+ }
+ }, { once: true });
+ });
+ const tabWindow =
+ window.open(baseURL + "window_css_visibility_propagation-4.html");
+ await tabReady;
+
+ const iframe = tabWindow.document.querySelector("iframe");
+ let target = iframe.contentDocument.getElementById("button");
+ target.focus();
+
+ ok(!tabWindow.browsingContext.isUnderHiddenEmbedderElement, "tab window is visible");
+ ok(iframe.browsingContext.isUnderHiddenEmbedderElement, "iframe is not visible");
+
+ isnot(iframe.contentDocument.activeElement, target,
+ "Element.focus() shouldn't work in invisible iframe");
+
+ tabWindow.close();
+});
+]]>
+</script>
+</window>
diff --git a/layout/base/tests/chrome/test_default_background.xhtml b/layout/base/tests/chrome/test_default_background.xhtml
new file mode 100644
index 0000000000..26e28c574e
--- /dev/null
+++ b/layout/base/tests/chrome/test_default_background.xhtml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?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">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+ <!-- test results are displayed in the html:body -->
+ <body xmlns="http://www.w3.org/1999/xhtml">
+ </body>
+
+ <!-- test code goes here -->
+ <script type="application/javascript">
+ <![CDATA[
+ SimpleTest.waitForExplicitFinish();
+ // Run the test in a separate window so that the test runs as a chrome
+ // window
+ window.openDialog("default_background_window.xhtml", "default_background",
+ "chrome,width=200,height=300,noopener", window);
+ ]]>
+ </script>
+</window>
diff --git a/layout/base/tests/chrome/test_dialog_with_positioning.html b/layout/base/tests/chrome/test_dialog_with_positioning.html
new file mode 100644
index 0000000000..db08a3d9b4
--- /dev/null
+++ b/layout/base/tests/chrome/test_dialog_with_positioning.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test positioning of fixed-pos/abs-pos elements in a XUL dialog</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/chrome-harness.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var root = getRootDirectory(window.location.href);
+window.openDialog(root + "dialog_with_positioning_window.xhtml", "dialog_with_positioning",
+ "dialog,chrome,noopener", window);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_document_adopted_styles.html b/layout/base/tests/chrome/test_document_adopted_styles.html
new file mode 100644
index 0000000000..f2784bd60b
--- /dev/null
+++ b/layout/base/tests/chrome/test_document_adopted_styles.html
@@ -0,0 +1,8 @@
+<!doctype html>
+<meta charset="utf-8">
+<div class="target"></div>
+<script>
+ const sheet = new CSSStyleSheet();
+ document.adoptedStyleSheets = [sheet];
+ sheet.replaceSync(".target { width: 100px; height: 100px; border-style: solid; border-color: blue; }");
+</script>
diff --git a/layout/base/tests/chrome/test_document_adopted_styles_ref.html b/layout/base/tests/chrome/test_document_adopted_styles_ref.html
new file mode 100644
index 0000000000..0b592207f3
--- /dev/null
+++ b/layout/base/tests/chrome/test_document_adopted_styles_ref.html
@@ -0,0 +1,6 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+ <div class="target"></div>
+ <style> .target { width: 100px; height: 100px; border-style: solid; border-color: blue; } </style>
+</body>
diff --git a/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html b/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html
new file mode 100644
index 0000000000..9543340d6b
--- /dev/null
+++ b/layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that we don't get unnecessary repaints with fixed backgrounds</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)" style="background:url(blue-32x32.png) top left no-repeat fixed; background-size: 100px 2000px; overflow:hidden;">
+<div style="height: 2048px"></div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var utils = window.windowUtils;
+
+function startTest() {
+ // Do a scroll to ensure we trigger activity heuristics.
+ document.documentElement.scrollTop = 1;
+ waitForAllPaintsFlushed(function () {
+ document.documentElement.scrollTop = 0;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state and scroll down
+ utils.checkAndClearPaintedState(document.documentElement);
+ document.documentElement.scrollTop = 100;
+ waitForAllPaintsFlushed(function () {
+ // Make sure nothing painted
+ var painted = utils.checkAndClearPaintedState(document.documentElement);
+ is(painted, false, "Fixed background should not have been painted when scrolled");
+ SimpleTest.finish();
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_getClientRectsAndTexts.html b/layout/base/tests/chrome/test_getClientRectsAndTexts.html
new file mode 100644
index 0000000000..d2fdde2197
--- /dev/null
+++ b/layout/base/tests/chrome/test_getClientRectsAndTexts.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<div id="div1" style="width:200px">Here is some text that <a href="#">will wrap</a> in <a href="#">this small</a>-ish container.</div>
+<div id="div2">Into another <a href="#">container</a></div>
+<div id="div3">A very <span>deep <span>deep <span>deep</span></span></span> bit of text.</div>
+
+<script>
+if (typeof(is) == "undefined") {
+ var is = function(a, b, m) {
+ if(a != b) {
+ window.console.log("Expected '" + b + "' but got '" + a + "': " + m);
+ }
+ };
+}
+
+if (typeof(todo_is) == "undefined") {
+ var todo_is = is;
+}
+
+function testRangeTexts(startNode, startOffset, endNode, endOffset, expectedText, todo) {
+ let r = new Range();
+ r.setStart(startNode, startOffset);
+ r.setEnd(endNode, endOffset);
+
+ let texts = r.getClientRectsAndTexts().textList;
+ let concatText = "";
+ for (let i = 0; i < texts.length; i++) {
+ concatText += texts[i];
+ }
+
+ if (todo) {
+ todo_is(concatText, expectedText, "Text matches.");
+ } else {
+ is(concatText, expectedText, "Text matches.");
+ }
+}
+
+let d1c1 = div1.firstChild;
+let d1c2 = d1c1.nextSibling;
+let d1c3 = d1c2.nextSibling;
+let d1c4 = d1c3.nextSibling;
+let d1c5 = d1c4.nextSibling;
+
+let link1 = d1c2.firstChild;
+let link2 = d1c4.firstChild;
+
+let d2c1 = div2.firstChild;
+let d2c2 = d2c1.nextSibling;
+
+let link3 = d2c2.firstChild;
+
+let d3c1 = div3.firstChild;
+let d3c2 = d3c1.nextSibling;
+let d3c3 = d3c2.nextSibling;
+
+let data = [
+ [d1c1, 0, d1c1, 0, ""],
+ [d1c1, 0, d1c1, 4, "Here"],
+ [d1c1, 4, d1c1, 7, " is"],
+ [d1c1, 22, link1, 0, " "],
+ [d1c1, 22, link1, 1, " w"],
+ [d1c1, 22, d1c3, 1, " will wrap "],
+ [link1, 2, link2, 3, "ll wrap in thi"],
+ [link2, 5, link3, 3, "small-ish container.Into another con"],
+ [d3c1, 3, d3c3, 4, "ery deep deep deep bit"],
+];
+
+data.forEach(function (d) { testRangeTexts.apply(null, d); });
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_get_printer_basic_attributes.html b/layout/base/tests/chrome/test_get_printer_basic_attributes.html
new file mode 100644
index 0000000000..26a04e09d5
--- /dev/null
+++ b/layout/base/tests/chrome/test_get_printer_basic_attributes.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="run()">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+async function run() {
+ try {
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].getService(
+ Ci.nsIPrinterList
+ );
+
+ let printers = await printerList.printers;
+ for (let printer of printers) {
+ printer.QueryInterface(Ci.nsIPrinter);
+ info(`Listing basic attributes for ${printer.name}:`);
+ let [supportsDuplex, supportsColor] = await Promise.all([printer.supportsDuplex, printer.supportsColor]);
+ info(`* supportsDuplex: ${supportsDuplex}`);
+ info(`* supportsColor: ${supportsColor}`);
+ }
+
+ ok(true, "Retrieved printer basic attributes successfully.");
+ } catch (e) {
+ ok(false, `Error thrown while retrieving printer basic attributes: ${e}.`);
+ console.error(e);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_get_printer_orientation.html b/layout/base/tests/chrome/test_get_printer_orientation.html
new file mode 100644
index 0000000000..1bb50eef65
--- /dev/null
+++ b/layout/base/tests/chrome/test_get_printer_orientation.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="run()">
+<script>
+
+SimpleTest.waitForExplicitFinish();
+
+async function run() {
+ try {
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].getService(
+ Ci.nsIPrinterList
+ );
+ var settingsSvc = Cc["@mozilla.org/gfx/printsettings-service;1"].getService(
+ Ci.nsIPrintSettingsService
+ );
+
+ let printers = await printerList.printers;
+ for (let printer of printers) {
+ printer.QueryInterface(Ci.nsIPrinter);
+ let printerInfo = await printer.printerInfo;
+
+ // Look up the printer's defaultSettings:
+ let defaultSettings = printerInfo.defaultSettings;
+
+ // Let the printer impose its defaults onto a fresh settings object:
+ let freshSettings = settingsSvc.createNewPrintSettings();
+ printerList.initPrintSettingsFromPrinter(printer.name, freshSettings);
+
+ // Make sure they agree on the default orientation:
+ is(freshSettings.orientation, defaultSettings.orientation,
+ "initPrintSettingsFromPrinter should produce the same orientation " +
+ "as the printer's defaultSettings");
+ }
+
+ // This ok() just lets us avoid failure-due-to-no-tests-being-run, on
+ // devices that have no printers available & hence skip the loop above:
+ ok(true, "Finished traversing printers.");
+ } catch (e) {
+ ok(false, `Error thrown while retrieving printer info: ${e}.`);
+ console.error(e);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_get_printer_paper_sizes.html b/layout/base/tests/chrome/test_get_printer_paper_sizes.html
new file mode 100644
index 0000000000..4ebe462ac6
--- /dev/null
+++ b/layout/base/tests/chrome/test_get_printer_paper_sizes.html
@@ -0,0 +1,69 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="run()">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+async function run() {
+ try {
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].getService(
+ Ci.nsIPrinterList
+ );
+ let printers = await printerList.printers;
+ if (printers.length == 0) {
+ ok(true, "There were no printers to iterate through.");
+ }
+
+ for (let printer of printers) {
+ printer.QueryInterface(Ci.nsIPrinter);
+ is(typeof(printer.name), 'string', "Printer name should be a string.");
+ isnot(printer.name, "", "Printer name should never be empty.");
+
+ info(printer.name);
+ info("duplex(" + printer.supportsDuplex + ")");
+
+ let printerInfo = await printer.printerInfo;
+ for (let paper of printerInfo.paperList) {
+ paper.QueryInterface(Ci.nsIPaper);
+
+ info(`${paper.name}: ${paper.width}x${paper.height}`);
+
+ is(typeof(paper.name), 'string', "Paper name should be a string.");
+ isnot(paper.name, "", "Paper name should never be empty.");
+
+ is(typeof(paper.width), 'number', "Paper width should be a number.");
+ ok(paper.width > 0.0, "Paper width should be greater than zero.");
+
+ is(typeof(paper.height), 'number', "Paper height should be a number.");
+ ok(paper.height > 0.0, "Paper height should be greater than zero.");
+
+ let margin = await paper.unwriteableMargin;
+ margin.QueryInterface(Ci.nsIPaperMargin);
+
+ info(`with margin: ${margin.top} ${margin.right} ${margin.bottom} ${margin.left}`);
+
+ is(typeof(margin.top), 'number', "Paper unwriteable margin top should be a number.");
+ is(typeof(margin.right), 'number', "Paper unwriteable margin right should be a number.");
+ is(typeof(margin.bottom), 'number', "Paper unwriteable margin bottom should be a number.");
+ is(typeof(margin.left), 'number', "Paper unwriteable margin left should be a number.");
+
+ ok(margin.top >= 0.0, "Paper unwriteable margin top should be greater than or equal to zero.");
+ ok(margin.right >= 0.0, "Paper unwriteable margin right should be greater than or equal to zero.");
+ ok(margin.bottom >= 0.0, "Paper unwriteable bottom right should be greater than or equal to zero.");
+ ok(margin.left >= 0.0, "Paper unwriteable margin left should be greater than or equal to zero.");
+ }
+ }
+ } catch (e) {
+ ok(false, `Shouldn't throw: ${e}`);
+ console.error(e);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_prerendered_transforms.html b/layout/base/tests/chrome/test_prerendered_transforms.html
new file mode 100644
index 0000000000..d8b8c8bcfd
--- /dev/null
+++ b/layout/base/tests/chrome/test_prerendered_transforms.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that active transformed elements coming into view are prerendered so we don't have to redraw constantly</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest()">
+<div>
+<div id="t" style="position:absolute; left:0; top:500px; transform: translatex(-100px); width:200px; height:100px; background:yellow;">
+ <div style="text-align:right">Hello</div>
+ <div style="text-align:left">Kitty</div>
+</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var utils = window.windowUtils;
+
+function startTest() {
+ // Do a couple of transform changes to ensure we've triggered activity heuristics
+ waitForAllPaintsFlushed(function () {
+ t.style.transform = "translatex(-75px)";
+ waitForAllPaintsFlushed(function () {
+ t.style.transform = "translatex(-50px)";
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and move again.
+ utils.checkAndClearPaintedState(t);
+ // Don't move to 0 since that might trigger some special case that turns off transforms.
+ t.style.transform = "translatex(-1px)";
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(t);
+ is(painted, false, "Transformed element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_printer_default_settings.html b/layout/base/tests/chrome/test_printer_default_settings.html
new file mode 100644
index 0000000000..8fb6f98a4e
--- /dev/null
+++ b/layout/base/tests/chrome/test_printer_default_settings.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body onload="run()">
+<script type="application/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+async function run() {
+ try {
+ let printerList = Cc["@mozilla.org/gfx/printerlist;1"].getService(
+ Ci.nsIPrinterList
+ );
+ let printers = await printerList.printers;
+ if (printers.length == 0) {
+ ok(true, "There were no printers to iterate through.");
+ }
+
+ for (let printer of printers) {
+ printer.QueryInterface(Ci.nsIPrinter);
+ info(printer.name);
+ info("duplex(" + await printer.supportsDuplex + ")");
+
+ const printerInfo = await printer.printerInfo;
+ const settings = printerInfo.defaultSettings;
+ settings.QueryInterface(Ci.nsIPrintSettings);
+
+ is(typeof settings.printerName, "string", "Printer name should be a string.");
+ is(settings.printerName, printer.name, "Print settings' printer should match the printer that created them.");
+
+ is(typeof settings.paperId, "string", "Paper ID should never be null.");
+ is(typeof settings.paperWidth, "number", "Paper width should never be null.");
+ is(typeof settings.paperHeight, "number", "Paper height should never be null.");
+
+ if (settings.paperId != "") {
+ info(`Paper: ${settings.paperId}`);
+ info(`Size: (${settings.paperWidth} x ${settings.paperHeight})`);
+ ok(settings.paperWidth > 0.0, "Paper width should be greater than zero.");
+ ok(settings.paperHeight > 0.0, "Paper height should be greater than zero.");
+ }
+
+ ok(settings.marginTop >= 0.0, "Paper margins should be greater than or equal to zero.");
+ ok(settings.marginRight >= 0.0, "Paper margins should be greater than or equal to zero.");
+ ok(settings.marginBottom >= 0.0, "Paper margins should be greater than or equal to zero.");
+ ok(settings.marginLeft >= 0.0, "Paper margins should be greater than or equal to zero.");
+
+ is(settings.printInColor, await printer.supportsColor, "Print settings' color mode should match the printer's color support.");
+
+ ok(settings.isInitializedFromPrinter, "Print settings were initialized from printer");
+ ok(!settings.isInitializedFromPrefs);
+ }
+ } catch (e) {
+ ok(false, `Shouldn't throw: ${e}`);
+ console.error(e);
+ }
+ SimpleTest.finish();
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_printpreview.xhtml b/layout/base/tests/chrome/test_printpreview.xhtml
new file mode 100644
index 0000000000..c63d7a62d1
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview.xhtml
@@ -0,0 +1,16 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?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">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+window.openDialog("printpreview_helper.xhtml", "printpreview", "chrome,width=100,height=100,noopener", window);
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_printpreview_bug396024.xhtml b/layout/base/tests/chrome/test_printpreview_bug396024.xhtml
new file mode 100644
index 0000000000..4b839f3b2b
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview_bug396024.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=396024
+-->
+<window title="Mozilla Bug 369024"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=396024"
+ target="_blank">Mozilla Bug 396024</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+window.openDialog("printpreview_bug396024_helper.xhtml", "bug396024", "chrome,width=100,height=100,noopener", window);
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_printpreview_bug482976.xhtml b/layout/base/tests/chrome/test_printpreview_bug482976.xhtml
new file mode 100644
index 0000000000..8dd4c65337
--- /dev/null
+++ b/layout/base/tests/chrome/test_printpreview_bug482976.xhtml
@@ -0,0 +1,21 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+ type="text/css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=482976
+-->
+<window title="Mozilla Bug 482976"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<body xmlns="http://www.w3.org/1999/xhtml">
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=482976"
+ target="_blank">Mozilla Bug 482976</a>
+</body>
+ <!-- test code goes here -->
+<script type="application/javascript">
+<![CDATA[
+SimpleTest.waitForExplicitFinish();
+window.openDialog("printpreview_bug482976_helper.xhtml", "bug482976", "chrome,width=100,height=100,noopener", window);
+]]></script>
+</window>
diff --git a/layout/base/tests/chrome/test_scrolling_repaints.html b/layout/base/tests/chrome/test_scrolling_repaints.html
new file mode 100644
index 0000000000..605e598c52
--- /dev/null
+++ b/layout/base/tests/chrome/test_scrolling_repaints.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that we don't get unnecessary repaints due to subpixel shifts</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)">
+<div id="t" style="width:400px; height:100px; background:yellow; overflow:hidden">
+ <div style="height:40px;"></div>
+ <div id="e" style="height:30px; background:lime"></div>
+ <div style="height:60.4px; background:pink"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = window.windowUtils;
+
+function startTest() {
+ // Do a scroll to ensure we trigger activity heuristics.
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ // Scroll down as far as we can, to put our rendering layer at a subpixel offset within the layer
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 1000;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ // scroll up a little bit. This should not cause anything to be repainted.
+ t.scrollTop = t.scrollTop - 10;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/chrome/test_shadow_root_adopted_styles.html b/layout/base/tests/chrome/test_shadow_root_adopted_styles.html
new file mode 100644
index 0000000000..d6701f3089
--- /dev/null
+++ b/layout/base/tests/chrome/test_shadow_root_adopted_styles.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+<script>
+ document.body.attachShadow({mode: "open"}).innerHTML = `
+ <div class="target"></div>
+ `;
+ const sheet = new CSSStyleSheet();
+ document.body.shadowRoot.adoptedStyleSheets = [sheet];
+ sheet.replaceSync(".target { width: 100px; height: 100px; border-style: solid; border-color: blue; }");
+</script>
diff --git a/layout/base/tests/chrome/test_shadow_root_adopted_styles_ref.html b/layout/base/tests/chrome/test_shadow_root_adopted_styles_ref.html
new file mode 100644
index 0000000000..fae4a54f21
--- /dev/null
+++ b/layout/base/tests/chrome/test_shadow_root_adopted_styles_ref.html
@@ -0,0 +1,11 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+<script>
+ document.body.attachShadow({mode: "open"}).innerHTML = `
+ <div class="target"></div>
+ <style>
+ .target { width: 100px; height: 100px; border-style: solid; border-color: blue; }
+ </style>
+ `;
+</script>
diff --git a/layout/base/tests/chrome/test_shared_adopted_styles.html b/layout/base/tests/chrome/test_shared_adopted_styles.html
new file mode 100644
index 0000000000..f5b232bce6
--- /dev/null
+++ b/layout/base/tests/chrome/test_shared_adopted_styles.html
@@ -0,0 +1,19 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+ <div class="target"></div>
+ <span id="shadowHostA"></span>
+ <span id="shadowHostB"></span>
+</body>
+<script>
+ const sheet = new CSSStyleSheet();
+ sheet.replaceSync(".target { width: 100px; height: 100px; border-style: solid; border-color: blue; }");
+
+ const innerHTMLText = `<div class="target"></div>`
+ shadowHostA.attachShadow({mode: "open"}).innerHTML = innerHTMLText;
+ shadowHostB.attachShadow({mode: "open"}).innerHTML = innerHTMLText;
+
+ document.adoptedStyleSheets = [sheet];
+ shadowHostA.shadowRoot.adoptedStyleSheets = [sheet];
+ shadowHostB.shadowRoot.adoptedStyleSheets = [sheet];
+</script>
diff --git a/layout/base/tests/chrome/test_shared_adopted_styles_ref.html b/layout/base/tests/chrome/test_shared_adopted_styles_ref.html
new file mode 100644
index 0000000000..b12cb5fd99
--- /dev/null
+++ b/layout/base/tests/chrome/test_shared_adopted_styles_ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<meta charset="utf-8">
+<body>
+ <div class="target"></div>
+ <span id="shadowHostA"></span>
+ <span id="shadowHostB"></span>
+ <style> .target { width: 100px; height: 100px; border-style: solid; border-color: blue; } </style>
+</body>
+<script>
+ const innerHTMLText = `
+ <div class="target"></div>
+ <style> .target { width: 100px; height: 100px; border-style: solid; border-color: blue; } </style>
+ `;
+ shadowHostA.attachShadow({mode: "open"}).innerHTML = innerHTMLText;
+ shadowHostB.attachShadow({mode: "open"}).innerHTML = innerHTMLText;
+</script>
diff --git a/layout/base/tests/chrome/test_will_change.html b/layout/base/tests/chrome/test_will_change.html
new file mode 100644
index 0000000000..fd34dc50f6
--- /dev/null
+++ b/layout/base/tests/chrome/test_will_change.html
@@ -0,0 +1,140 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for MozAfterPaint</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/paint_listener.js"></script>
+ <style>
+ div {
+ width: 100px;
+ height: 100px;
+ background: radial-gradient(ellipse at center, #87e0fd 0%,#53cbf1 40%,#05abe0 100%);
+ }
+ </style>
+</head>
+<body>
+</body>
+<script>
+
+var utils = window.windowUtils;
+
+function waitForPaints() {
+ return new Promise(function(resolve, reject) {
+ waitForAllPaintsFlushed(resolve);
+ });
+}
+
+add_task(async () => {
+ var element = document.createElement("div");
+ document.body.appendChild(element);
+
+ await waitForPaints();
+
+ utils.checkAndClearPaintedState(element);
+ element.style.opacity = "0.5";
+
+ await waitForPaints();
+
+ var painted = utils.checkAndClearPaintedState(element);
+ // *** We check that this repaints because the test is relying
+ // on this property. If this is broken then this test wont
+ // be reliable check for will-change.
+ is(painted, true, "element should have been painted");
+
+ element.remove();
+});
+
+add_task(async () => {
+ var element = document.createElement("div");
+ document.body.appendChild(element);
+
+ element.style.willChange = "opacity";
+
+ await waitForPaints();
+
+ utils.checkAndClearPaintedState(element);
+ element.style.opacity = "0.5";
+
+ await waitForPaints();
+
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:opacity,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change:opacity element should not have been painted");
+ }
+
+ element.remove();
+});
+
+add_task(async () => {
+ var element = document.createElement("div");
+ document.body.appendChild(element);
+
+ element.style.willChange = "transform";
+
+ await waitForPaints();
+
+ utils.checkAndClearPaintedState(element);
+ element.style.transform = "translateY(-5px)";
+
+ await waitForPaints();
+
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:transform,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change:transform element should not have been painted");
+ }
+
+ element.remove();
+});
+
+add_task(async () => {
+ var element = document.createElement("div");
+ document.body.appendChild(element);
+
+ element.style.willChange = "translate";
+
+ await waitForPaints();
+
+ utils.checkAndClearPaintedState(element);
+ element.style.translate = "5px";
+
+ await waitForPaints();
+
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:translate,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change:translate element should not have been painted");
+ }
+
+ element.remove();
+});
+
+add_task(async () => {
+ var element = document.createElement("div");
+ document.body.appendChild(element);
+
+ element.style.willChange = "offset-path";
+
+ await waitForPaints();
+
+ utils.checkAndClearPaintedState(element);
+ element.style.offsetPath = "path('M55 50 h1')";
+
+ await waitForPaints();
+
+ var painted = utils.checkAndClearPaintedState(element);
+ // BasicLayers' heuristics are so that even with will-change:offset-path,
+ // we can still have repaints.
+ if (utils.layerManagerType != "Basic") {
+ is(painted, false, "will-change:offset-path element should not have been painted");
+ }
+
+ element.remove();
+});
+
+</script>
+</html>
diff --git a/layout/base/tests/chrome/window_css_visibility_propagation-1.xhtml b/layout/base/tests/chrome/window_css_visibility_propagation-1.xhtml
new file mode 100644
index 0000000000..ac9c63ec14
--- /dev/null
+++ b/layout/base/tests/chrome/window_css_visibility_propagation-1.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<div id="parent" style="visibility:hidden">
+ <browser type="content" src="about:mozilla"></browser>
+</div>
+</window>
diff --git a/layout/base/tests/chrome/window_css_visibility_propagation-2.xhtml b/layout/base/tests/chrome/window_css_visibility_propagation-2.xhtml
new file mode 100644
index 0000000000..9b9e42c2d1
--- /dev/null
+++ b/layout/base/tests/chrome/window_css_visibility_propagation-2.xhtml
@@ -0,0 +1,6 @@
+<?xml version="1.0"?>
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<div id="parent">
+ <browser type="content" src="frame_css_visibility_propagation.html"></browser>
+</div>
+</window>
diff --git a/layout/base/tests/chrome/window_css_visibility_propagation-3.html b/layout/base/tests/chrome/window_css_visibility_propagation-3.html
new file mode 100644
index 0000000000..91a2230ee1
--- /dev/null
+++ b/layout/base/tests/chrome/window_css_visibility_propagation-3.html
@@ -0,0 +1,3 @@
+<div id="parent">
+ <iframe onload="opener.postMessage('ready');" src="window_css_visibility_propagation-2.xhtml"/>
+</div>
diff --git a/layout/base/tests/chrome/window_css_visibility_propagation-4.html b/layout/base/tests/chrome/window_css_visibility_propagation-4.html
new file mode 100644
index 0000000000..98de74059c
--- /dev/null
+++ b/layout/base/tests/chrome/window_css_visibility_propagation-4.html
@@ -0,0 +1,3 @@
+<div id="parent" style="visibility:hidden">
+ <iframe onload="opener.postMessage('ready');" src="frame_css_visibility_propagation.html"/>
+</div>
diff --git a/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input-ref.html b/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input-ref.html
new file mode 100644
index 0000000000..3d56d9c500
--- /dev/null
+++ b/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input-ref.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Move Selection into an editing host before TextEditor gets blur event</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div[contenteditable] {
+ outline: none;
+ }
+ input {
+ border: none;
+ outline: none;
+ }
+</style>
+<script>
+SimpleTest.waitForFocus(() => {
+ const editingHost = document.querySelector("div[contenteditable]");
+ editingHost.addEventListener("focus", () => {
+ requestAnimationFrame(
+ () => document.documentElement.removeAttribute("class")
+ );
+ }, { once: true });
+ getSelection().collapse(editingHost, 0);
+});
+</script>
+<input>
+<div contenteditable="true" spellcheck="false"><br></div>
diff --git a/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input.html b/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input.html
new file mode 100644
index 0000000000..bed3636684
--- /dev/null
+++ b/layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input.html
@@ -0,0 +1,33 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Move Selection into an editing host before TextEditor gets blur event</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div[contenteditable] {
+ outline: none;
+ }
+ input {
+ border: none;
+ outline: none;
+ }
+</style>
+<script>
+SimpleTest.waitForFocus(() => {
+ const input = document.querySelector("input");
+ input.focus();
+ input.addEventListener("blur", () => {
+ const editingHost = document.querySelector("div[contenteditable]");
+ editingHost.addEventListener("focus", () => {
+ requestAnimationFrame(
+ () => document.documentElement.removeAttribute("class")
+ );
+ }, { once: true });
+ getSelection().collapse(editingHost, 0);
+
+ }, { once: true });
+ requestAnimationFrame(() => input.blur());
+});
+</script>
+<input>
+<div contenteditable="true" spellcheck="false"><br></div>
diff --git a/layout/base/tests/file_bug607529-1.html b/layout/base/tests/file_bug607529-1.html
new file mode 100644
index 0000000000..506b7092c5
--- /dev/null
+++ b/layout/base/tests/file_bug607529-1.html
@@ -0,0 +1,12 @@
+<script>
+ var bc_1 = new BroadcastChannel("bug607529_1");
+ bc_1.onmessage = (msgEvent) => {
+ if (msgEvent.data == "navigateBack") {
+ bc_1.close();
+ history.back();
+ }
+ }
+ window.onload = function() {
+ bc_1.postMessage('goback');
+ }
+</script>
diff --git a/layout/base/tests/file_bug607529.html b/layout/base/tests/file_bug607529.html
new file mode 100644
index 0000000000..bebcd09de9
--- /dev/null
+++ b/layout/base/tests/file_bug607529.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<script>
+ var bc = new BroadcastChannel("bug607529");
+ var closed = false;
+ window.onerror = function(msg, url, line) {
+ var myMsg = JSON.stringify({msg: msg, url: url, line: line, error: true});
+ bc.postMessage(myMsg);
+ }
+
+ var report = false;
+
+ function g() {
+ if (report && !closed) {
+ bc.postMessage("callbackHappened");
+ }
+ window.requestAnimationFrame(g);
+ }
+ g();
+
+ bc.onmessage = function (e) {
+ var msg = e.data;
+ if (msg == "report") {
+ report = true;
+ } else if (msg == "navigateToPage") {
+ window.location = "file_bug607529-1.html";
+ } else if (msg == "close") {
+ bc.postMessage("closed");
+ bc.close();
+ closed = true;
+ window.close();
+ }
+ };
+
+ window.onload = function() {
+ bc.postMessage("loaded");
+ }
+
+ addEventListener("pagehide", function f(e) {
+ if (!e.persisted && !report) {
+ bc.postMessage("notcached");
+ }
+ }, false);
+
+ addEventListener("pageshow", function f(e) {
+ if (e.persisted) {
+ bc.postMessage("revived");
+ }
+ }, false);
+
+
+</script>
diff --git a/layout/base/tests/file_bug842853-frame.html b/layout/base/tests/file_bug842853-frame.html
new file mode 100644
index 0000000000..8da930232c
--- /dev/null
+++ b/layout/base/tests/file_bug842853-frame.html
@@ -0,0 +1,6 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+<a href="#anchor">Click to scroll to anchor</a><div style="height:5000px"></div><a name="anchor">FAIL</a>
+</body>
+</html>
diff --git a/layout/base/tests/file_bug842853.html b/layout/base/tests/file_bug842853.html
new file mode 100644
index 0000000000..5f216f8ade
--- /dev/null
+++ b/layout/base/tests/file_bug842853.html
@@ -0,0 +1,17 @@
+<!DOCTYPE HTML>
+<html><head>
+ <meta charset="utf-8">
+ <title>Testcase for bug </title>
+<link rel="stylesheet" href="file_bug842853.sjs">
+</head>
+<body>
+
+<a href="#anchor">Click to scroll to anchor</a><div style="height:5000px"></div><a name="anchor">FAIL</a>
+<script>
+window.addEventListener("load", () => {
+ window.parent.runTest();
+});
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/file_bug842853.sjs b/layout/base/tests/file_bug842853.sjs
new file mode 100644
index 0000000000..0ae70f7df5
--- /dev/null
+++ b/layout/base/tests/file_bug842853.sjs
@@ -0,0 +1,16 @@
+var timer;
+
+function handleRequest(request, response) {
+ response.setHeader("Cache-Control", "no-cache, must-revalidate", false);
+ response.setHeader("Content-Type", "text/css", false);
+ response.write("body { background:lime; color:red; }");
+ response.processAsync();
+ timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ timer.initWithCallback(
+ function () {
+ response.finish();
+ },
+ 500,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+}
diff --git a/layout/base/tests/file_dynamic_toolbar_max_height.html b/layout/base/tests/file_dynamic_toolbar_max_height.html
new file mode 100644
index 0000000000..0b99ef496c
--- /dev/null
+++ b/layout/base/tests/file_dynamic_toolbar_max_height.html
@@ -0,0 +1,56 @@
+<!doctype html>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
+<title>Tests metrics with dynamic toolbar</title>
+<script>
+const ok = opener.ok.bind(opener);
+const is = opener.is.bind(opener);
+const original_finish = opener.SimpleTest.finish;
+const SimpleTest = opener.SimpleTest;
+const add_task = opener.add_task;
+SimpleTest.finish = function finish() {
+ self.close();
+ original_finish();
+}
+</script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css">
+<style>
+html {
+ scrollbar-width: none;
+}
+#percent {
+ position: absolute;
+ height: 100%;
+}
+#vh {
+ position: absolute;
+ height: 100vh;
+}
+</style>
+<div id="percent"></div>
+<div id="vh"></div>
+<script>
+'use strict';
+
+SpecialPowers.DOMWindowUtils.setDynamicToolbarMaxHeight(0);
+
+let percentHeight = getComputedStyle(percent).height;
+let vhHeight = getComputedStyle(vh).height;
+is(percentHeight, vhHeight,
+ "%-units and vh-units should be the same when the dynamic toolbar max " +
+ "height is zero");
+
+SpecialPowers.DOMWindowUtils.setDynamicToolbarMaxHeight(50);
+
+percentHeight = getComputedStyle(percent).height;
+vhHeight = getComputedStyle(vh).height;
+is(parseInt(percentHeight) + 50, parseInt(vhHeight),
+ "vh units should be 50px greater than %-units");
+is(document.documentElement.clientHeight, parseInt(percentHeight),
+ "documentElement.clientHeight should equal to %-units");
+ok(matchMedia(`(height: ${percentHeight})`).matches,
+ "Media Queries' height is not including the dynamic toolbar max height");
+
+SimpleTest.finish();
+
+</script>
diff --git a/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame1.html b/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame1.html
new file mode 100644
index 0000000000..f072b65117
--- /dev/null
+++ b/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame1.html
@@ -0,0 +1,3 @@
+<!DOCTYPE HTML>
+<html style='padding:25px'>
+<div id='f1d' style='position:absolute; left:14px; top:15px; width:16px; height:17px; background:pink'></div>
diff --git a/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame2.html b/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame2.html
new file mode 100644
index 0000000000..a2ebad8183
--- /dev/null
+++ b/layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame2.html
@@ -0,0 +1 @@
+<div id='d'>
diff --git a/layout/base/tests/file_lazyload_telemetry.html b/layout/base/tests/file_lazyload_telemetry.html
new file mode 100644
index 0000000000..473822c37f
--- /dev/null
+++ b/layout/base/tests/file_lazyload_telemetry.html
@@ -0,0 +1,9 @@
+<!DOCTYPE HTML>
+<html>
+ <body>
+ <img id="image1" src="image_rgrg-256x256.png" loading="lazy" style="display:block; width:100px; height:50vh; margin-bottom: 1px;">
+ <img id="image2" src="image_rgrg-256x256.png" loading="lazy" style="display:block; width:100px; height:50vh; margin-bottom: 1px;">
+ <img id="image3" src="image_rgrg-256x256.png" loading="lazy" style="display:block; width:100px; height:50vh; margin-bottom: 1px;">
+ <img id="image4" src="image_rgrg-256x256.png" loading="lazy" style="display:block; width:100px; height:50vh; margin-bottom: 1px;">
+ </body>
+</html>
diff --git a/layout/base/tests/file_stylesheet_change_events.html b/layout/base/tests/file_stylesheet_change_events.html
new file mode 100644
index 0000000000..3639d4bda3
--- /dev/null
+++ b/layout/base/tests/file_stylesheet_change_events.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <title>Document for Bug 839103</title>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style></style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/base/tests/file_synthmousemove.html b/layout/base/tests/file_synthmousemove.html
new file mode 100644
index 0000000000..74d327dbe9
--- /dev/null
+++ b/layout/base/tests/file_synthmousemove.html
@@ -0,0 +1,49 @@
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<style>
+ .spacer {
+ height: 50px;
+ }
+ .special {
+ height: 200px;
+ background-color: blue;
+ }
+ .special:hover {
+ background-color: red;
+ }
+</style>
+<div id="x" class="spacer"></div>
+<div id="x2" class="special"></div>
+<div style="margin-top: 100vh">
+ <!-- this is here so it wrongly gets sent the events -->
+ <iframe src="https://example.com/tests/layout/base/tests/helper_synthmousemove.html"></iframe>
+</div>
+<div style="height: 300vh"></div>
+<script>
+
+async function runTest() {
+ let thex = document.getElementById("x");
+ let thex2 = document.getElementById("x2")
+ synthesizeMouse(thex, 20, 20, {type: "mousemove"});
+ opener.is(
+ getComputedStyle(thex2).backgroundColor,
+ "rgb(0, 0, 255)",
+ "Part is blue"
+ );
+
+ thex.remove();
+ document.documentElement.getBoundingClientRect();
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+ opener.is(
+ getComputedStyle(thex2).backgroundColor,
+ "rgb(255, 0, 0)",
+ "Part is red"
+ );
+
+ opener.reportSuccess();
+ window.close();
+}
+
+SimpleTest.waitForFocus(runTest);
+</script>
diff --git a/layout/base/tests/file_zoom_restore_bfcache.html b/layout/base/tests/file_zoom_restore_bfcache.html
new file mode 100644
index 0000000000..77451f3ef6
--- /dev/null
+++ b/layout/base/tests/file_zoom_restore_bfcache.html
@@ -0,0 +1,92 @@
+<!doctype html>
+<script>
+ var bcName = "zoomRestoreBfcache" + window.location.search;
+ var bc = new BroadcastChannel(bcName);
+ if (window.location.search == "?2") {
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ dump(`Subpage ?2 received command=${command}\n`);
+ switch (command) {
+ case "case2sendData": {
+ bc.postMessage({command: "case2data", devicePixelRatio: window.devicePixelRatio,
+ frameDevicePixelRatio: document.querySelector("iframe").contentWindow.devicePixelRatio});
+ break;
+ }
+ case "case2action": {
+ SpecialPowers.spawnChrome([], () => {
+ const FullZoom = this.browsingContext.embedderElement.ownerGlobal.FullZoom;
+ FullZoom.setZoom(2.0);
+ });
+ SpecialPowers.setFullZoom(window, 2);
+ window.requestAnimationFrame(() => window.requestAnimationFrame(() => {
+ bc.postMessage({command: "case2dataAnimationFrame", devicePixelRatio: window.devicePixelRatio,
+ frameDevicePixelRatio: document.querySelector("iframe").contentWindow.devicePixelRatio });
+ }));
+ break;
+ }
+ case "case2back": {
+ bc.close();
+ window.history.back();
+ break;
+ }
+ }
+ }
+ } else {
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ dump(`Subpage received command=${command}\n`);
+ switch (command) {
+ case "case1sendData": {
+ bc.postMessage({command: "case1data", devicePixelRatio: window.devicePixelRatio});
+ break;
+ }
+ case "case1click": {
+ document.querySelector("a").click();
+ // We are opening file_zoom_restore_bfcache.html?2, so the current
+ // page is going into bfcache
+ break;
+ }
+ case "case3sendData": {
+ // We came back from bfcache
+ SpecialPowers.spawnChrome([], () => {
+ // We use FullZoom to set the zoom level in the parent, but if FullZoom is not
+ // available then that will fail. So we don't wait here for the devicePixelRatio
+ // to change if FullZoom is not available, and test_zoom_restore_bfcache.html
+ // will mark this test as todo.
+ return "FullZoom" in this.browsingContext.embedderElement.ownerGlobal;
+ }).then((hasFullZoom) => {
+ function waitUntilZoomLevelRestored() {
+ // Zoom level is updated asynchronously when bfcache lives in the
+ // parent process.
+ if (!hasFullZoom || window.devicePixelRatio == 2) {
+ bc.postMessage({command: "case3data", devicePixelRatio: window.devicePixelRatio,
+ frameDevicePixelRatio: document.querySelector("iframe").contentWindow.devicePixelRatio});
+ return;
+ }
+ window.requestAnimationFrame(waitUntilZoomLevelRestored);
+ }
+ window.requestAnimationFrame(waitUntilZoomLevelRestored);
+ });
+ break;
+ }
+ case "close": {
+ SpecialPowers.spawnChrome([], () => {
+ const FullZoom = this.browsingContext.embedderElement.ownerGlobal.FullZoom;
+ FullZoom.setZoom(1.0);
+ });
+ bc.postMessage({command: "closed"});
+ bc.close();
+ window.close();
+ break;
+ }
+ }
+ }
+ }
+ window.addEventListener("pageshow", function(e) {
+ bc.postMessage({command: "handlePageShow", eventPersisted: e.persisted});
+ });
+</script>
+<a href="?2">This is a very interesting page</a>
+<iframe srcdoc="And this is a nested frame"></iframe>
diff --git a/layout/base/tests/helper_bug1701027-1.html b/layout/base/tests/helper_bug1701027-1.html
new file mode 100644
index 0000000000..659c1f7826
--- /dev/null
+++ b/layout/base/tests/helper_bug1701027-1.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body>
+Here is some text to stare at as the test runs. It serves no functional
+purpose
+<img src="notoifnd" style="position: absolute;">
+<div id="fd" style="position: fixed; left:0; top:0;bottom:0;right:0;"></div>
+</body>
+</html>
diff --git a/layout/base/tests/helper_bug1701027-2.html b/layout/base/tests/helper_bug1701027-2.html
new file mode 100644
index 0000000000..659c1f7826
--- /dev/null
+++ b/layout/base/tests/helper_bug1701027-2.html
@@ -0,0 +1,10 @@
+<html>
+<head>
+</head>
+<body>
+Here is some text to stare at as the test runs. It serves no functional
+purpose
+<img src="notoifnd" style="position: absolute;">
+<div id="fd" style="position: fixed; left:0; top:0;bottom:0;right:0;"></div>
+</body>
+</html>
diff --git a/layout/base/tests/helper_synthmousemove.html b/layout/base/tests/helper_synthmousemove.html
new file mode 100644
index 0000000000..41d8c6525a
--- /dev/null
+++ b/layout/base/tests/helper_synthmousemove.html
@@ -0,0 +1,3 @@
+<!doctype html>
+<title>helper_synthmousemove.html</title>
+<div></div>
diff --git a/layout/base/tests/image_rgrg-256x256.png b/layout/base/tests/image_rgrg-256x256.png
new file mode 100644
index 0000000000..e6fba3daa5
--- /dev/null
+++ b/layout/base/tests/image_rgrg-256x256.png
Binary files differ
diff --git a/layout/base/tests/input-invalid-ref.html b/layout/base/tests/input-invalid-ref.html
new file mode 100644
index 0000000000..4b34c9a2f3
--- /dev/null
+++ b/layout/base/tests/input-invalid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:red">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-maxlength-invalid-change.html b/layout/base/tests/input-maxlength-invalid-change.html
new file mode 100644
index 0000000000..849445f85f
--- /dev/null
+++ b/layout/base/tests/input-maxlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey("KEY_Backspace");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="2" value="fooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-ui-invalid-change.html b/layout/base/tests/input-maxlength-ui-invalid-change.html
new file mode 100644
index 0000000000..1f74f0730c
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is -moz-ui-invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey("KEY_Backspace");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="2" value="fooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-ui-valid-change.html b/layout/base/tests/input-maxlength-ui-valid-change.html
new file mode 100644
index 0000000000..47224772fa
--- /dev/null
+++ b/layout/base/tests/input-maxlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is -moz-ui-valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey("KEY_Backspace"); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey("KEY_Backspace");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="3" value="foooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-maxlength-valid-before-change.html b/layout/base/tests/input-maxlength-valid-before-change.html
new file mode 100644
index 0000000000..8662e8f5f4
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is valid until the user edits it, even if it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <input id="input" maxlength="2" value="foo">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-maxlength-valid-change.html b/layout/base/tests/input-maxlength-valid-change.html
new file mode 100644
index 0000000000..2612642534
--- /dev/null
+++ b/layout/base/tests/input-maxlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with maxlength is valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ synthesizeKey("KEY_Backspace"); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ synthesizeKey("KEY_Backspace");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" maxlength="3" value="foooo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-invalid-change.html b/layout/base/tests/input-minlength-invalid-change.html
new file mode 100644
index 0000000000..543c3e335d
--- /dev/null
+++ b/layout/base/tests/input-minlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ sendString("o");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="4" value="fo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-ui-invalid-change.html b/layout/base/tests/input-minlength-ui-invalid-change.html
new file mode 100644
index 0000000000..6c5dfc0e22
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is -moz-ui-invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ sendString("o");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="4" value="fo">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-ui-valid-change.html b/layout/base/tests/input-minlength-ui-valid-change.html
new file mode 100644
index 0000000000..96e6390b91
--- /dev/null
+++ b/layout/base/tests/input-minlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is -moz-ui-valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ sendString("o"); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ sendString("o");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="3" value="f">
+ </body>
+</html>
diff --git a/layout/base/tests/input-minlength-valid-before-change.html b/layout/base/tests/input-minlength-valid-before-change.html
new file mode 100644
index 0000000000..21e6927926
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is valid until the user edits it, even if it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <input id="input" minlength="5" value="foo">
+ </body>
+</html>
+
diff --git a/layout/base/tests/input-minlength-valid-change.html b/layout/base/tests/input-minlength-valid-change.html
new file mode 100644
index 0000000000..92b7fd3390
--- /dev/null
+++ b/layout/base/tests/input-minlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: input with minlength is valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var input = document.getElementById('input');
+ input.setSelectionRange(input.value.length, input.value.length)
+ input.focus();
+ sendString("o"); // so that it becomes invalid first
+ input.blur();
+ input.focus();
+ sendString("o");
+ input.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <input id="input" minlength="3" value="f">
+ </body>
+</html>
diff --git a/layout/base/tests/input-password-RTL-input-ref.html b/layout/base/tests/input-password-RTL-input-ref.html
new file mode 100644
index 0000000000..f3b5efe3ae
--- /dev/null
+++ b/layout/base/tests/input-password-RTL-input-ref.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<input type="password">
+<script>
+ function runTest() {
+ let hash = window.location.hash;
+ let input = document.getElementsByTagName("input")[0];
+ input.focus();
+ synthesizeKey("a");
+ synthesizeKey("b");
+ synthesizeKey("c");
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/input-password-RTL-input.html b/layout/base/tests/input-password-RTL-input.html
new file mode 100644
index 0000000000..3c73d3ef1c
--- /dev/null
+++ b/layout/base/tests/input-password-RTL-input.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+<body>
+<input type="password">
+<script>
+ function runTest() {
+ let hash = window.location.hash;
+ let input = document.getElementsByTagName("input")[0];
+ input.focus();
+ synthesizeKey("a");
+ synthesizeKey("b");
+ switch (hash) {
+ case "#arabic":
+ synthesizeKey("\u0634");
+ break;
+ case "#hebrew":
+ synthesizeKey("\u05D3");
+ break;
+ }
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/input-password-remask-ref.html b/layout/base/tests/input-password-remask-ref.html
new file mode 100644
index 0000000000..a7f105a01e
--- /dev/null
+++ b/layout/base/tests/input-password-remask-ref.html
@@ -0,0 +1,20 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="password" value="123456">
+<script>
+ function runTest() {
+ let input = document.getElementsByTagName("input")[0];
+ input.focus();
+ input.setSelectionRange(3, 4);
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/input-password-remask.html b/layout/base/tests/input-password-remask.html
new file mode 100644
index 0000000000..31149eac8d
--- /dev/null
+++ b/layout/base/tests/input-password-remask.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="password" value="abcdef">
+<script>
+ function runTest() {
+ let input = document.getElementsByTagName("input")[0];
+ input.focus();
+ input.setSelectionRange(3, 4);
+ let editor = SpecialPowers.wrap(input).editor;
+ editor.unmask(0, 6);
+ editor.mask();
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/input-password-unmask-around-emoji-ref.html b/layout/base/tests/input-password-unmask-around-emoji-ref.html
new file mode 100644
index 0000000000..1fba0d9ae1
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-around-emoji-ref.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="text">
+<script>
+ function runTest() {
+ let params = window.location.hash.substring(1).split("-");
+ let input = document.getElementsByTagName("input")[0];
+ let editor = SpecialPowers.wrap(input).editor;
+ let unmaskStart = Number.parseInt(params[0]);
+ let unmaskEnd = Number.parseInt(params[1]);
+ let value = "";
+ if (unmaskStart == 0 && unmaskEnd > 0) {
+ value += "a";
+ } else {
+ value += editor.passwordMask;
+ }
+ if (unmaskStart <= 2 && unmaskEnd > 1 && unmaskEnd > unmaskStart) {
+ value += String.fromCodePoint(0x1f914);
+ } else {
+ value += editor.passwordMask;
+ value += editor.passwordMask;
+ }
+ if (unmaskStart <= 3 && unmaskEnd > 3) {
+ value += "b";
+ } else {
+ value += editor.passwordMask;
+ }
+ input.value = value;
+ input.setSelectionRange(Number.parseInt(params[2]), Number.parseInt(params[3]));
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/input-password-unmask-around-emoji.html b/layout/base/tests/input-password-unmask-around-emoji.html
new file mode 100644
index 0000000000..97df884850
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-around-emoji.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="password" value="a&#x1f914;b">
+<script>
+ function runTest() {
+ let params = window.location.hash.substring(1).split("-");
+ let input = document.getElementsByTagName("input")[0];
+ let editor = SpecialPowers.wrap(input).editor;
+ editor.unmask(Number.parseInt(params[0]), Number.parseInt(params[1]));
+ input.setSelectionRange(Number.parseInt(params[2]), Number.parseInt(params[3]));
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/input-password-unmask-ref.html b/layout/base/tests/input-password-unmask-ref.html
new file mode 100644
index 0000000000..d1803551e4
--- /dev/null
+++ b/layout/base/tests/input-password-unmask-ref.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="text">
+<script>
+ function runTest() {
+ let params = window.location.hash.split("-");
+ let input = document.getElementsByTagName("input")[0];
+ let editor = SpecialPowers.wrap(input).editor;
+ let unmaskStart = Number.parseInt(params[1]);
+ let unmaskEnd = Number.parseInt(params[2]);
+ let value = "";
+ for (let i = 0; i < params[0].length - 1; i++) {
+ let c = params[0].charAt(i + 1);
+ if (i < unmaskStart || i >= unmaskEnd) {
+ c = editor.passwordMask;
+ } else if (c === "_") {
+ c = " ";
+ }
+ value += c;
+ }
+ input.value = value;
+ input.setSelectionRange(Number.parseInt(params[3]), Number.parseInt(params[4]));
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/input-password-unmask.html b/layout/base/tests/input-password-unmask.html
new file mode 100644
index 0000000000..34c63726bb
--- /dev/null
+++ b/layout/base/tests/input-password-unmask.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait">
+<head>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<input type="password">
+<script>
+ function runTest() {
+ let params = window.location.hash.split("-");
+ let input = document.getElementsByTagName("input")[0];
+ input.value = params[0].replace("_", " ").substring(1);
+ let editor = SpecialPowers.wrap(input).editor;
+ editor.unmask(Number.parseInt(params[1]), Number.parseInt(params[2]));
+ input.setSelectionRange(Number.parseInt(params[3]), Number.parseInt(params[4]));
+ if (params.length > 5) {
+ input.style.textTransform = params[5];
+ }
+ document.documentElement.removeAttribute("class");
+ }
+
+ SimpleTest.waitForFocus(runTest);
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/input-stoppropagation-ref.html b/layout/base/tests/input-stoppropagation-ref.html
new file mode 100644
index 0000000000..99ff791588
--- /dev/null
+++ b/layout/base/tests/input-stoppropagation-ref.html
@@ -0,0 +1,16 @@
+<!doctype html>
+<html>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function onLoad() {
+ document.getElementById("input1").focus();
+ synthesizeKey("KEY_Tab");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ synthesizeKey("KEY_Tab");
+}
+</script>
+<body onload="onLoad()">
+ <input type="text" id="input1"></input>
+ <input type="text" id="input2"></input>
+</body>
+</html>
diff --git a/layout/base/tests/input-stoppropagation.html b/layout/base/tests/input-stoppropagation.html
new file mode 100644
index 0000000000..b246a6b6da
--- /dev/null
+++ b/layout/base/tests/input-stoppropagation.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<script>
+function onLoad() {
+ let input2 = document.getElementById("input2");
+ input2.addEventListener("focus", e => {
+ e.stopImmediatePropagation();
+ });
+ document.getElementById("input1").focus();
+ synthesizeKey("KEY_Tab");
+ synthesizeKey("KEY_Tab", {shiftKey: true});
+ synthesizeKey("KEY_Tab");
+}
+</script>
+<body onload="onLoad()">
+ <input type="text" id="input1"></input>
+ <input type="text" id="input2"></input>
+</body>
+</html>
diff --git a/layout/base/tests/input-ui-valid-ref.html b/layout/base/tests/input-ui-valid-ref.html
new file mode 100644
index 0000000000..76d9386678
--- /dev/null
+++ b/layout/base/tests/input-ui-valid-ref.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:green">
+ </body>
+</html>
diff --git a/layout/base/tests/input-valid-ref.html b/layout/base/tests/input-valid-ref.html
new file mode 100644
index 0000000000..ec01bb98f2
--- /dev/null
+++ b/layout/base/tests/input-valid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <input value="foo" style="background-color:green">
+ </body>
+</html>
+
diff --git a/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html b/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html
new file mode 100644
index 0000000000..55eccf814f
--- /dev/null
+++ b/layout/base/tests/interlinePosition-after-Selection-addRange-ref.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Selection.addRange() should always reset interline position</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div[contenteditable] {
+ padding: 1em;
+ }
+</style>
+<script>
+SimpleTest.waitForFocus(async () => {
+ const editingHost = document.querySelector("div[contenteditable]");
+ editingHost.focus();
+ getSelection().collapse(editingHost, 2);
+ SpecialPowers.wrap(getSelection()).interlinePosition = true;
+ document.documentElement.removeAttribute("class");
+});
+</script>
+<div contenteditable="true" spellcheck="false">abc<br><br></div>
diff --git a/layout/base/tests/interlinePosition-after-Selection-addRange.html b/layout/base/tests/interlinePosition-after-Selection-addRange.html
new file mode 100644
index 0000000000..8e45277249
--- /dev/null
+++ b/layout/base/tests/interlinePosition-after-Selection-addRange.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<html class="reftest-wait">
+<meta charset="utf-8">
+<title>Selection.addRange() should always reset interline position</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<style>
+ div[contenteditable] {
+ padding: 1em;
+ }
+</style>
+<script>
+SimpleTest.waitForFocus(async () => {
+ const editingHost = document.querySelector("div[contenteditable]");
+ editingHost.focus();
+ getSelection().collapse(editingHost, 2);
+ SpecialPowers.wrap(getSelection()).interlinePosition = false;
+ getSelection().addRange(getSelection().getRangeAt(0));
+ document.documentElement.removeAttribute("class");
+});
+</script>
+<div contenteditable="true" spellcheck="false">abc<br><br></div>
diff --git a/layout/base/tests/marionette/manifest.toml b/layout/base/tests/marionette/manifest.toml
new file mode 100644
index 0000000000..045e0648e4
--- /dev/null
+++ b/layout/base/tests/marionette/manifest.toml
@@ -0,0 +1,7 @@
+[DEFAULT]
+prefs = ["gfx.font_loader.delay=0"]
+run-if = ["buildapp == 'browser'"]
+
+["test_accessiblecaret_cursor_mode.py"]
+
+["test_accessiblecaret_selection_mode.py"]
diff --git a/layout/base/tests/marionette/selection.py b/layout/base/tests/marionette/selection.py
new file mode 100644
index 0000000000..dd391972fb
--- /dev/null
+++ b/layout/base/tests/marionette/selection.py
@@ -0,0 +1,363 @@
+# -*- coding: utf-8 -*-
+# 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 marionette_driver.marionette import Actions, errors
+
+
+class CaretActions(Actions):
+ def __init__(self, marionette):
+ super(CaretActions, self).__init__(marionette)
+ self._reset_action_chain()
+
+ def _reset_action_chain(self):
+ # Release the action so that two consecutive clicks won't become a
+ # double-click.
+ self.release()
+
+ self.mouse_chain = self.sequence(
+ "pointer", "pointer_id", {"pointerType": "mouse"}
+ )
+ self.key_chain = self.sequence("key", "keyboard_id")
+
+ def move(self, element, x, y, duration=None):
+ """Queue a pointer_move action.
+
+ :param element: an element where its top-left corner is the origin of
+ the coordinates.
+ :param x: Destination x-axis coordinate in CSS pixels.
+ :param y: Destination y-axis coordinate in CSS pixels.
+ :param duration: Number of milliseconds over which to distribute the
+ move. If None, remote end defaults to 0.
+
+ """
+ rect = element.rect
+ el_x, el_y = rect["x"], rect["y"]
+
+ # Add the element's top-left corner (el_x, el_y) to make the coordinate
+ # relative to the viewport.
+ dest_x, dest_y = int(el_x + x), int(el_y + y)
+
+ self.mouse_chain.pointer_move(dest_x, dest_y, duration=duration)
+ return self
+
+ def click(self, element=None):
+ """Queue a click action.
+
+ If an element is given, move the pointer to that element first,
+ otherwise click current pointer coordinates.
+
+ :param element: Optional element to click.
+
+ """
+ self.mouse_chain.click(element=element)
+ return self
+
+ def flick(self, element, x1, y1, x2, y2, duration=200):
+ """Queue a flick gesture on the target element.
+
+ :param element: The element to perform the flick gesture on. Its
+ top-left corner is the origin of the coordinates.
+ :param x1: Starting x-axis coordinate of the flick in CSS Pixels.
+ :param y1: Starting y-axis coordinate of the flick in CSS Pixels.
+ :param x2: Ending x-axis coordinate of the flick in CSS Pixels.
+ :param y2: Ending y-axis coordinate of the flick in CSS Pixels.
+
+ """
+ self.move(element, x1, y1, duration=0)
+ self.mouse_chain.pointer_down()
+ self.move(element, x2, y2, duration=duration)
+ self.mouse_chain.pointer_up()
+ return self
+
+ def send_keys(self, keys):
+ """Perform a keyDown and keyUp action for each character in `keys`.
+
+ :param keys: String of keys to perform key actions with.
+
+ """
+ self.key_chain.send_keys(keys)
+ return self
+
+ def perform(self):
+ """Perform the action chain built so far to the server side for execution
+ and clears the current chain of actions.
+
+ Warning: This method performs all the mouse actions before all the key
+ actions!
+
+ """
+ self.mouse_chain.perform()
+ self.key_chain.perform()
+ self._reset_action_chain()
+
+
+class SelectionManager(object):
+ """Interface for manipulating the selection and carets of the element.
+
+ We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
+ caret for short.
+
+ Simple usage example:
+
+ ::
+
+ element = marionette.find_element(By.ID, 'input')
+ sel = SelectionManager(element)
+ sel.move_caret_to_front()
+
+ """
+
+ def __init__(self, element):
+ self.element = element
+
+ def _input_or_textarea(self):
+ """Return True if element is either <input> or <textarea>."""
+ return self.element.tag_name in ("input", "textarea")
+
+ def js_selection_cmd(self):
+ """Return a command snippet to get selection object.
+
+ If the element is <input> or <textarea>, return the selection object
+ associated with it. Otherwise, return the current selection object.
+
+ Note: "element" must be provided as the first argument to
+ execute_script().
+
+ """
+ if self._input_or_textarea():
+ # We must unwrap sel so that DOMRect could be returned to Python
+ # side.
+ return """var sel = arguments[0].editor.selection;"""
+ else:
+ return """var sel = window.getSelection();"""
+
+ def move_cursor_by_offset(self, offset, backward=False):
+ """Move cursor in the element by character offset.
+
+ :param offset: Move the cursor to the direction by offset characters.
+ :param backward: Optional, True to move backward; Default to False to
+ move forward.
+
+ """
+ cmd = (
+ self.js_selection_cmd()
+ + """
+ for (let i = 0; i < {0}; ++i) {{
+ sel.modify("move", "{1}", "character");
+ }}
+ """.format(
+ offset, "backward" if backward else "forward"
+ )
+ )
+
+ self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox="system"
+ )
+
+ def move_cursor_to_front(self):
+ """Move cursor in the element to the front of the content."""
+ if self._input_or_textarea():
+ cmd = """arguments[0].setSelectionRange(0, 0);"""
+ else:
+ cmd = """var sel = window.getSelection();
+ sel.collapse(arguments[0].firstChild, 0);"""
+
+ self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox=None
+ )
+
+ def move_cursor_to_end(self):
+ """Move cursor in the element to the end of the content."""
+ if self._input_or_textarea():
+ cmd = """var len = arguments[0].value.length;
+ arguments[0].setSelectionRange(len, len);"""
+ else:
+ cmd = """var sel = window.getSelection();
+ sel.collapse(arguments[0].lastChild, arguments[0].lastChild.length);"""
+
+ self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox=None
+ )
+
+ def selection_rect_list(self, idx):
+ """Return the selection's DOMRectList object for the range at given idx.
+
+ If the element is either <input> or <textarea>, return the DOMRectList of
+ the range at given idx of the selection within the element. Otherwise,
+ return the DOMRectList of the of the range at given idx of current selection.
+
+ """
+ cmd = (
+ self.js_selection_cmd()
+ + """return sel.getRangeAt({}).getClientRects();""".format(idx)
+ )
+ return self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox="system"
+ )
+
+ def range_count(self):
+ """Get selection's range count"""
+ cmd = self.js_selection_cmd() + """return sel.rangeCount;"""
+ return self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox="system"
+ )
+
+ def _selection_location_helper(self, location_type):
+ """Return the start and end location of the selection in the element.
+
+ Return a tuple containing two pairs of (x, y) coordinates of the start
+ and end locations in the element. The coordinates are relative to the
+ top left-hand corner of the element. Both ltr and rtl directions are
+ considered.
+
+ """
+ range_count = self.range_count()
+ if range_count <= 0:
+ raise errors.MarionetteException(
+ "Expect at least one range object in Selection, but found nothing!"
+ )
+
+ # FIXME (Bug 1682382): We shouldn't need the retry for-loops if
+ # selection_rect_list() can reliably return a valid list.
+ retry_times = 3
+ for _ in range(retry_times):
+ try:
+ first_rect_list = self.selection_rect_list(0)
+ first_rect = first_rect_list["0"]
+ break
+ except KeyError:
+ continue
+ else:
+ raise errors.MarionetteException(
+ "Expect at least one rect in the first range, but found nothing!"
+ )
+
+ for _ in range(retry_times):
+ try:
+ # Making a selection over some non-selectable elements can
+ # create multiple ranges.
+ last_rect_list = (
+ first_rect_list
+ if range_count == 1
+ else self.selection_rect_list(range_count - 1)
+ )
+ last_list_length = last_rect_list["length"]
+ last_rect = last_rect_list[str(last_list_length - 1)]
+ break
+ except KeyError:
+ continue
+ else:
+ raise errors.MarionetteException(
+ "Expect at least one rect in the last range, but found nothing!"
+ )
+
+ origin_x, origin_y = self.element.rect["x"], self.element.rect["y"]
+
+ if self.element.get_property("dir") == "rtl": # such as Arabic
+ start_pos, end_pos = "right", "left"
+ else:
+ start_pos, end_pos = "left", "right"
+
+ # Calculate y offset according to different needs.
+ if location_type == "center":
+ start_y_offset = first_rect["height"] / 2.0
+ end_y_offset = last_rect["height"] / 2.0
+ elif location_type == "caret":
+ # Selection carets' tip are below the bottom of the two ends of the
+ # selection. Add 5px to y should be sufficient to locate them.
+ caret_tip_y_offset = 5
+ start_y_offset = first_rect["height"] + caret_tip_y_offset
+ end_y_offset = last_rect["height"] + caret_tip_y_offset
+ else:
+ start_y_offset = end_y_offset = 0
+
+ caret1_x = first_rect[start_pos] - origin_x
+ caret1_y = first_rect["top"] + start_y_offset - origin_y
+ caret2_x = last_rect[end_pos] - origin_x
+ caret2_y = last_rect["top"] + end_y_offset - origin_y
+
+ return ((caret1_x, caret1_y), (caret2_x, caret2_y))
+
+ def selection_location(self):
+ """Return the start and end location of the selection in the element.
+
+ Return a tuple containing two pairs of (x, y) coordinates of the start
+ and end of the selection. The coordinates are relative to the top
+ left-hand corner of the element. Both ltr and rtl direction are
+ considered.
+
+ """
+ return self._selection_location_helper("center")
+
+ def carets_location(self):
+ """Return a pair of the two carets' location.
+
+ Return a tuple containing two pairs of (x, y) coordinates of the two
+ carets' tip. The coordinates are relative to the top left-hand corner of
+ the element. Both ltr and rtl direction are considered.
+
+ """
+ return self._selection_location_helper("caret")
+
+ def cursor_location(self):
+ """Return the blanking cursor's center location within the element.
+
+ Return (x, y) coordinates of the cursor's center relative to the top
+ left-hand corner of the element.
+
+ """
+ return self._selection_location_helper("center")[0]
+
+ def first_caret_location(self):
+ """Return the first caret's location.
+
+ Return (x, y) coordinates of the first caret's tip relative to the top
+ left-hand corner of the element.
+
+ """
+ return self.carets_location()[0]
+
+ def second_caret_location(self):
+ """Return the second caret's location.
+
+ Return (x, y) coordinates of the second caret's tip relative to the top
+ left-hand corner of the element.
+
+ """
+ return self.carets_location()[1]
+
+ def select_all(self):
+ """Select all the content in the element."""
+ if self._input_or_textarea():
+ cmd = """var len = arguments[0].value.length;
+ arguments[0].focus();
+ arguments[0].setSelectionRange(0, len);"""
+ else:
+ cmd = """var range = document.createRange();
+ range.setStart(arguments[0].firstChild, 0);
+ range.setEnd(arguments[0].lastChild, arguments[0].lastChild.length);
+ var sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);"""
+
+ self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox=None
+ )
+
+ @property
+ def content(self):
+ """Return all the content of the element."""
+ if self._input_or_textarea():
+ return self.element.get_property("value")
+ else:
+ return self.element.text
+
+ @property
+ def selected_content(self):
+ """Return the selected portion of the content in the element."""
+ cmd = self.js_selection_cmd() + """return sel.toString();"""
+ return self.element.marionette.execute_script(
+ cmd, script_args=(self.element,), sandbox="system"
+ )
diff --git a/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
new file mode 100644
index 0000000000..a134b78f73
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py
@@ -0,0 +1,280 @@
+# -*- coding: utf-8 -*-
+# 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/.
+
+import string
+import sys
+import os
+
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from selection import (
+ CaretActions,
+ SelectionManager,
+)
+from marionette_driver.by import By
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ parameterized,
+)
+
+
+class AccessibleCaretCursorModeTestCase(MarionetteTestCase):
+ """Test cases for AccessibleCaret under cursor mode.
+
+ We call the blinking cursor (nsCaret) as cursor, and call AccessibleCaret as
+ caret for short.
+
+ """
+
+ # Element IDs.
+ _input_id = "input"
+ _input_padding_id = "input-padding"
+ _textarea_id = "textarea"
+ _textarea_one_line_id = "textarea-one-line"
+ _contenteditable_id = "contenteditable"
+
+ # Test html files.
+ _cursor_html = "layout/test_carets_cursor.html"
+
+ def setUp(self):
+ # Code to execute before every test is running.
+ super(AccessibleCaretCursorModeTestCase, self).setUp()
+ self.caret_tested_pref = "layout.accessiblecaret.enabled"
+ self.hide_carets_for_mouse = (
+ "layout.accessiblecaret.hide_carets_for_mouse_input"
+ )
+ self.prefs = {
+ self.caret_tested_pref: True,
+ self.hide_carets_for_mouse: False,
+ # To disable transition, or the caret may not be the desired
+ # location yet, we cannot press a caret successfully.
+ "layout.accessiblecaret.transition-duration": "0.0",
+ # Enabled hapticfeedback on all platforms. The tests shouldn't crash
+ # on platforms without hapticfeedback support.
+ "layout.accessiblecaret.hapticfeedback": True,
+ }
+ self.marionette.set_prefs(self.prefs)
+ self.actions = CaretActions(self.marionette)
+
+ def tearDown(self):
+ self.marionette.actions.release()
+ super(AccessibleCaretCursorModeTestCase, self).tearDown()
+
+ def open_test_html(self, test_html):
+ self.marionette.navigate(self.marionette.absolute_url(test_html))
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_the_right_by_one_character(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ target_content = sel.content
+ target_content = target_content[:1] + content_to_add + target_content[1:]
+
+ # Get first caret (x, y) at position 1 and 2.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ cursor0_x, cursor0_y = sel.cursor_location()
+ first_caret0_x, first_caret0_y = sel.first_caret_location()
+ sel.move_cursor_by_offset(1)
+ first_caret1_x, first_caret1_y = sel.first_caret_location()
+
+ # Click the front of the input to make first caret appear.
+ self.actions.move(el, cursor0_x, cursor0_y).click().perform()
+
+ # Move first caret.
+ self.actions.flick(
+ el, first_caret0_x, first_caret0_y, first_caret1_x, first_caret1_y
+ ).perform()
+
+ self.actions.send_keys(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_end_by_dragging_caret_to_bottom_right_corner(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ target_content = sel.content + content_to_add
+
+ # Click the front of the input to make first caret appear.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+
+ # Move first caret to the bottom-right corner of the element.
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = el.rect["width"], el.rect["height"]
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.send_keys(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_move_cursor_to_front_by_dragging_caret_to_front(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ target_content = content_to_add + sel.content
+
+ # Get first caret location at the front.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ dest_x, dest_y = sel.first_caret_location()
+
+ # Click to make first caret appear.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_end()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+ src_x, src_y = sel.first_caret_location()
+
+ # Move first caret to the front of the input box.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.send_keys(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ def test_caret_not_appear_when_typing_in_scrollable_content(self):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, self._input_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ non_target_content = content_to_add + sel.content + string.ascii_letters
+
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_end()
+
+ # Insert a long string to the end of the <input>, which triggers
+ # ScrollPositionChanged event.
+ el.send_keys(string.ascii_letters)
+
+ # The caret should not be visible. If it does appear wrongly due to the
+ # ScrollPositionChanged event, we can drag it to the front of the
+ # <input> to change the cursor position.
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = 0, 0
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ # The content should not be inserted at the front of the <input>.
+ el.send_keys(content_to_add)
+
+ self.assertNotEqual(non_target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_caret_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ non_target_content = sel.content + content_to_add
+
+ # Goal: the cursor position is not changed after dragging the caret down
+ # on the Y-axis.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+ x, y = sel.first_caret_location()
+
+ # Drag the caret down by 50px, and insert '!'.
+ self.actions.flick(el, x, y, x, y + 50).perform()
+ self.actions.send_keys(content_to_add).perform()
+ self.assertNotEqual(non_target_content, sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_caret_not_jump_to_front_when_dragging_up_to_editable_content_boundary(
+ self, el_id
+ ):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ non_target_content = content_to_add + sel.content
+
+ # Goal: the cursor position is not changed after dragging the caret down
+ # on the Y-axis.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_end()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+ x, y = sel.first_caret_location()
+
+ # Drag the caret up by 40px, and insert '!'.
+ self.actions.flick(el, x, y, x, y - 40).perform()
+ self.actions.send_keys(content_to_add).perform()
+ self.assertNotEqual(non_target_content, sel.content)
+
+ def test_drag_caret_from_front_to_end_across_columns(self):
+ self.open_test_html("layout/test_carets_columns.html")
+ el = self.marionette.find_element(By.ID, "columns-inner")
+ sel = SelectionManager(el)
+ content_to_add = "!"
+ target_content = sel.content + content_to_add
+
+ # Goal: the cursor position can be changed by dragging the caret from
+ # the front to the end of the content.
+
+ # Click to make the cursor appear.
+ before_image_1 = self.marionette.find_element(By.ID, "before-image-1")
+ self.actions.click(element=before_image_1).perform()
+
+ # Click the front of the content to make first caret appear.
+ sel.move_cursor_to_front()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+ src_x, src_y = sel.first_caret_location()
+ dest_x, dest_y = el.rect["width"], el.rect["height"]
+
+ # Drag the first caret to the bottom-right corner of the element.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.send_keys(content_to_add).perform()
+ self.assertEqual(target_content, sel.content)
+
+ def test_move_cursor_to_front_by_dragging_caret_to_front_br_element(self):
+ self.open_test_html(self._cursor_html)
+ el = self.marionette.find_element(By.ID, self._contenteditable_id)
+ sel = SelectionManager(el)
+ content_to_add_1 = "!"
+ content_to_add_2 = "\n\n"
+ target_content = content_to_add_1 + content_to_add_2 + sel.content
+
+ # Goal: the cursor position can be changed by dragging the caret from
+ # the end of the content to the front br element. Because we cannot get
+ # caret location if it's on a br element, we need to get the first caret
+ # location then adding the new lines.
+
+ # Get first caret location at the front.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ dest_x, dest_y = sel.first_caret_location()
+
+ # Append new line to the front of the content.
+ el.send_keys(content_to_add_2)
+
+ # Click to make first caret appear.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_end()
+ self.actions.move(el, *sel.cursor_location()).click().perform()
+ src_x, src_y = sel.first_caret_location()
+
+ # Move first caret to the front of the input box.
+ self.actions.flick(el, src_x, src_y, dest_x, dest_y).perform()
+
+ self.actions.send_keys(content_to_add_1).perform()
+ self.assertEqual(target_content, sel.content)
diff --git a/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
new file mode 100644
index 0000000000..e3d815f62b
--- /dev/null
+++ b/layout/base/tests/marionette/test_accessiblecaret_selection_mode.py
@@ -0,0 +1,805 @@
+# -*- coding: utf-8 -*-
+# 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/.
+
+import re
+import sys
+import os
+import unittest
+
+# Add this directory to the import path.
+sys.path.append(os.path.dirname(__file__))
+
+from selection import (
+ CaretActions,
+ SelectionManager,
+)
+from marionette_driver.by import By
+from marionette_driver.keys import Keys
+from marionette_harness.marionette_test import (
+ MarionetteTestCase,
+ SkipTest,
+ parameterized,
+)
+
+
+class AccessibleCaretSelectionModeTestCase(MarionetteTestCase):
+ """Test cases for AccessibleCaret under selection mode."""
+
+ # Element IDs.
+ _input_id = "input"
+ _input_padding_id = "input-padding"
+ _input_size_id = "input-size"
+ _textarea_id = "textarea"
+ _textarea2_id = "textarea2"
+ _textarea_disabled_id = "textarea-disabled"
+ _textarea_one_line_id = "textarea-one-line"
+ _textarea_rtl_id = "textarea-rtl"
+ _contenteditable_id = "contenteditable"
+ _contenteditable2_id = "contenteditable2"
+ _content_id = "content"
+ _content2_id = "content2"
+ _non_selectable_id = "non-selectable"
+
+ # Test html files.
+ _selection_html = "layout/test_carets_selection.html"
+ _multipleline_html = "layout/test_carets_multipleline.html"
+ _multiplerange_html = "layout/test_carets_multiplerange.html"
+ _longtext_html = "layout/test_carets_longtext.html"
+ _iframe_html = "layout/test_carets_iframe.html"
+ _iframe_scroll_html = "layout/test_carets_iframe_scroll.html"
+ _display_none_html = "layout/test_carets_display_none.html"
+ _svg_shapes_html = "layout/test_carets_svg_shapes.html"
+ _key_scroll_html = "layout/test_carets_key_scroll.html"
+
+ def setUp(self):
+ # Code to execute before every test is running.
+ super(AccessibleCaretSelectionModeTestCase, self).setUp()
+ self.carets_tested_pref = "layout.accessiblecaret.enabled"
+ self.prefs = {
+ "layout.word_select.eat_space_to_next_word": False,
+ self.carets_tested_pref: True,
+ # To disable transition, or the caret may not be the desired
+ # location yet, we cannot press a caret successfully.
+ "layout.accessiblecaret.transition-duration": "0.0",
+ # Enabled hapticfeedback on all platforms. The tests shouldn't crash
+ # on platforms without hapticfeedback support.
+ "layout.accessiblecaret.hapticfeedback": True,
+ }
+ self.marionette.set_prefs(self.prefs)
+ self.actions = CaretActions(self.marionette)
+
+ def tearDown(self):
+ self.marionette.actions.release()
+ super(AccessibleCaretSelectionModeTestCase, self).tearDown()
+
+ def open_test_html(self, test_html):
+ self.marionette.navigate(self.marionette.absolute_url(test_html))
+
+ def word_offset(self, text, ordinal):
+ "Get the character offset of the ordinal-th word in text."
+ tokens = re.split(r"(\S+)", text) # both words and spaces
+ spaces = tokens[0::2] # collect spaces at odd indices
+ words = tokens[1::2] # collect word at even indices
+
+ if ordinal >= len(words):
+ raise IndexError(
+ "Only %d words in text, but got ordinal %d" % (len(words), ordinal)
+ )
+
+ # Cursor position of the targeting word is behind the the first
+ # character in the word. For example, offset to 'def' in 'abc def' is
+ # between 'd' and 'e'.
+ offset = len(spaces[0]) + 1
+ offset += sum(len(words[i]) + len(spaces[i + 1]) for i in range(ordinal))
+ return offset
+
+ def test_word_offset(self):
+ text = " " * 3 + "abc" + " " * 3 + "def"
+
+ self.assertTrue(self.word_offset(text, 0), 4)
+ self.assertTrue(self.word_offset(text, 1), 10)
+ with self.assertRaises(IndexError):
+ self.word_offset(text, 2)
+
+ def word_location(self, el, ordinal):
+ """Get the location (x, y) of the ordinal-th word in el.
+
+ The ordinal starts from 0.
+
+ Note: this function has a side effect which changes focus to the
+ target element el.
+
+ """
+ sel = SelectionManager(el)
+ offset = self.word_offset(sel.content, ordinal)
+
+ # Move the blinking cursor to the word.
+ self.actions.click(element=el).perform()
+ sel.move_cursor_to_front()
+ sel.move_cursor_by_offset(offset)
+ x, y = sel.cursor_location()
+
+ return x, y
+
+ def rect_relative_to_window(self, el):
+ """Get element's bounding rectangle.
+
+ This function is similar to el.rect, but the coordinate is relative to
+ the top left corner of the window instead of the document.
+
+ """
+ return self.marionette.execute_script(
+ """
+ let rect = arguments[0].getBoundingClientRect();
+ return {x: rect.x, y:rect.y, width: rect.width, height: rect.height};
+ """,
+ script_args=[el],
+ )
+
+ def long_press_on_location(self, el, x=None, y=None):
+ """Long press the location (x, y) to select a word.
+
+ If no (x, y) are given, it will be targeted at the center of the
+ element. On Windows, those spaces after the word will also be selected.
+ This function sends synthesized eMouseLongTap to gecko.
+
+ """
+ rect = self.rect_relative_to_window(el)
+ target_x = rect["x"] + (x if x is not None else rect["width"] // 2)
+ target_y = rect["y"] + (y if y is not None else rect["height"] // 2)
+
+ self.marionette.execute_script(
+ """
+ let utils = window.windowUtils;
+ utils.sendTouchEventToWindow('touchstart', [0],
+ [arguments[0]], [arguments[1]],
+ [1], [1], [0], [1], [0], [0], [0], 0);
+ utils.sendMouseEventToWindow('mouselongtap', arguments[0], arguments[1],
+ 0, 1, 0);
+ utils.sendTouchEventToWindow('touchend', [0],
+ [arguments[0]], [arguments[1]],
+ [1], [1], [0], [1], [0], [0], [0], 0);
+ """,
+ script_args=[target_x, target_y],
+ sandbox="system",
+ )
+
+ def long_press_on_word(self, el, wordOrdinal):
+ x, y = self.word_location(el, wordOrdinal)
+ self.long_press_on_location(el, x, y)
+
+ def to_unix_line_ending(self, s):
+ """Changes all Windows/Mac line endings in s to UNIX line endings."""
+
+ return s.replace("\r\n", "\n").replace("\r", "\n")
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_long_press_to_select_a_word(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_long_press_to_select_a_word(el)
+
+ def _test_long_press_to_select_a_word(self, el):
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 2, "Expect at least two words in the content.")
+ target_content = words[0]
+
+ # Goal: Select the first word.
+ self.long_press_on_word(el, 0)
+
+ # Ignore extra spaces selected after the word.
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_drag_carets(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")
+
+ # Goal: Select all text after the first word.
+ target_content = original_content[len(words[0]) :]
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ self.actions.click(element=el).perform()
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(el, 0)
+
+ # Drag the second caret to the end of the content.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()
+
+ # Drag the first caret to the previous position of the second caret.
+ self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_drag_swappable_carets(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")
+
+ target_content1 = words[0]
+ target_content2 = original_content[len(words[0]) :]
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ self.actions.click(element=el).perform()
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(el, 0)
+
+ # Drag the first caret to the end and back to where it was
+ # immediately. The selection range should not be collapsed.
+ caret1_x, caret1_y = sel.first_caret_location()
+ self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).flick(
+ el, end_caret_x, end_caret_y, caret1_x, caret1_y
+ ).perform()
+ self.assertEqual(target_content1, sel.selected_content)
+
+ # Drag the first caret to the end.
+ caret1_x, caret1_y = sel.first_caret_location()
+ self.actions.flick(el, caret1_x, caret1_y, end_caret_x, end_caret_y).perform()
+ self.assertEqual(target_content2, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ # Bug 1855083: Skip textarea tests due to high frequent intermittent.
+ # @parameterized(_textarea_id, el_id=_textarea_id)
+ # @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_minimum_select_one_character(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_minimum_select_one_character(el)
+
+ @parameterized(_textarea2_id, el_id=_textarea2_id)
+ @parameterized(_contenteditable2_id, el_id=_contenteditable2_id)
+ @parameterized(_content2_id, el_id=_content2_id)
+ def test_minimum_select_one_character2(self, el_id):
+ self.open_test_html(self._multipleline_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ self._test_minimum_select_one_character(el)
+
+ def _test_minimum_select_one_character(self, el):
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")
+
+ # Get the location of the carets at the end of the content for later
+ # use.
+ sel.select_all()
+ end_caret_x, end_caret_y = sel.second_caret_location()
+ self.actions.click(element=el).perform()
+
+ # Goal: Select the first character.
+ target_content = original_content[0]
+
+ self.long_press_on_word(el, 0)
+
+ # Drag the second caret to the end of the content.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, end_caret_x, end_caret_y).perform()
+
+ # Drag the second caret to the position of the first caret.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(
+ _input_id + "_to_" + _textarea_id, el1_id=_input_id, el2_id=_textarea_id
+ )
+ @parameterized(
+ _input_id + "_to_" + _contenteditable_id,
+ el1_id=_input_id,
+ el2_id=_contenteditable_id,
+ )
+ @parameterized(
+ _input_id + "_to_" + _content_id, el1_id=_input_id, el2_id=_content_id
+ )
+ @parameterized(
+ _textarea_id + "_to_" + _input_id, el1_id=_textarea_id, el2_id=_input_id
+ )
+ @parameterized(
+ _textarea_id + "_to_" + _contenteditable_id,
+ el1_id=_textarea_id,
+ el2_id=_contenteditable_id,
+ )
+ @parameterized(
+ _textarea_id + "_to_" + _content_id, el1_id=_textarea_id, el2_id=_content_id
+ )
+ @parameterized(
+ _contenteditable_id + "_to_" + _input_id,
+ el1_id=_contenteditable_id,
+ el2_id=_input_id,
+ )
+ @parameterized(
+ _contenteditable_id + "_to_" + _textarea_id,
+ el1_id=_contenteditable_id,
+ el2_id=_textarea_id,
+ )
+ @parameterized(
+ _contenteditable_id + "_to_" + _content_id,
+ el1_id=_contenteditable_id,
+ el2_id=_content_id,
+ )
+ @parameterized(
+ _content_id + "_to_" + _input_id, el1_id=_content_id, el2_id=_input_id
+ )
+ @parameterized(
+ _content_id + "_to_" + _textarea_id, el1_id=_content_id, el2_id=_textarea_id
+ )
+ @parameterized(
+ _content_id + "_to_" + _contenteditable_id,
+ el1_id=_content_id,
+ el2_id=_contenteditable_id,
+ )
+ def test_long_press_changes_focus_from(self, el1_id, el2_id):
+ self.open_test_html(self._selection_html)
+ el1 = self.marionette.find_element(By.ID, el1_id)
+ el2 = self.marionette.find_element(By.ID, el2_id)
+
+ # Compute the content of the first word in el2.
+ sel = SelectionManager(el2)
+ original_content = sel.content
+ words = original_content.split()
+ target_content = words[0]
+
+ # Goal: Click to focus el1, and then select the first word on el2.
+
+ # We want to collect the location of the first word in el2 here
+ # since self.word_location() has the side effect which would
+ # change the focus.
+ x, y = self.word_location(el2, 0)
+
+ self.actions.click(element=el1).perform()
+ self.long_press_on_location(el2, x, y)
+ self.assertEqual(target_content, sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_focus_not_changed_by_long_press_on_non_selectable(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ non_selectable = self.marionette.find_element(By.ID, self._non_selectable_id)
+
+ # Goal: Focus remains on the editable element el after long pressing on
+ # the non-selectable element.
+ sel = SelectionManager(el)
+ self.long_press_on_word(el, 0)
+ self.long_press_on_location(non_selectable)
+ active_sel = SelectionManager(self.marionette.get_active_element())
+ self.assertEqual(sel.content, active_sel.content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_textarea_id, el_id=_textarea_id)
+ @parameterized(_textarea_disabled_id, el_id=_textarea_disabled_id)
+ @parameterized(_textarea_rtl_id, el_id=_textarea_rtl_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ @parameterized(_content_id, el_id=_content_id)
+ def test_handle_tilt_when_carets_overlap_each_other(self, el_id):
+ """Test tilt handling when carets overlap to each other.
+
+ Let the two carets overlap each other. If they are set to tilted
+ successfully, click on the tilted carets should not cause the selection
+ to be collapsed and the carets should be draggable.
+
+ """
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 1, "Expect at least one word in the content.")
+
+ # Goal: Select the first word.
+ self.long_press_on_word(el, 0)
+ target_content = sel.selected_content
+
+ # Drag the first caret to the position of the second caret to trigger
+ # carets overlapping.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret1_x, caret1_y, caret2_x, caret2_y).perform()
+
+ # We make two hit tests targeting the left edge of the left tilted caret
+ # and the right edge of the right tilted caret. If either of the hits is
+ # missed, selection would be collapsed and both carets should not be
+ # draggable.
+ (caret3_x, caret3_y), (caret4_x, caret4_y) = sel.carets_location()
+
+ # The following values are from ua.css and all.js
+ caret_width = float(self.marionette.get_pref("layout.accessiblecaret.width"))
+ caret_margin_left = float(
+ self.marionette.get_pref("layout.accessiblecaret.margin-left")
+ )
+ tilt_right_margin_left = 0.41 * caret_width
+ tilt_left_margin_left = -0.39 * caret_width
+
+ left_caret_left_edge_x = caret3_x + caret_margin_left + tilt_left_margin_left
+ self.actions.move(el, left_caret_left_edge_x + 2, caret3_y).click().perform()
+
+ right_caret_right_edge_x = (
+ caret4_x + caret_margin_left + tilt_right_margin_left + caret_width
+ )
+ self.actions.move(el, right_caret_right_edge_x - 2, caret4_y).click().perform()
+
+ # Drag the first caret back to the initial selection, the first word.
+ self.actions.flick(el, caret3_x, caret3_y, caret1_x, caret1_y).perform()
+
+ self.assertEqual(target_content, sel.selected_content)
+
+ def test_drag_caret_over_non_selectable_field(self):
+ """Test dragging the caret over a non-selectable field.
+
+ The selected content should exclude non-selectable elements and the
+ second caret should appear in last range's position.
+
+ """
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, "bd")
+ sel3 = self.marionette.find_element(By.ID, "sel3")
+ sel4 = self.marionette.find_element(By.ID, "sel4")
+ sel6 = self.marionette.find_element(By.ID, "sel6")
+
+ # Select target element and get target caret location
+ self.long_press_on_word(sel4, 3)
+ sel = SelectionManager(body)
+ end_caret_x, end_caret_y = sel.second_caret_location()
+
+ self.long_press_on_word(sel6, 0)
+ end_caret2_x, end_caret2_y = sel.second_caret_location()
+
+ # Select start element
+ self.long_press_on_word(sel3, 3)
+
+ # Drag end caret to target location
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(
+ body, caret2_x, caret2_y, end_caret_x, end_caret_y, 1
+ ).perform()
+ self.assertEqual(
+ self.to_unix_line_ending(sel.selected_content.strip()),
+ "this 3\nuser can select this",
+ )
+
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(
+ body, caret2_x, caret2_y, end_caret2_x, end_caret2_y, 1
+ ).perform()
+ self.assertEqual(
+ self.to_unix_line_ending(sel.selected_content.strip()),
+ "this 3\nuser can select this 4\nuser can select this 5\nuser",
+ )
+
+ # Drag first caret to target location
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(
+ body, caret1_x, caret1_y, end_caret_x, end_caret_y, 1
+ ).perform()
+ self.assertEqual(
+ self.to_unix_line_ending(sel.selected_content.strip()),
+ "4\nuser can select this 5\nuser",
+ )
+
+ def test_drag_swappable_caret_over_non_selectable_field(self):
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, "bd")
+ sel3 = self.marionette.find_element(By.ID, "sel3")
+ sel4 = self.marionette.find_element(By.ID, "sel4")
+ sel = SelectionManager(body)
+
+ self.long_press_on_word(sel4, 3)
+ (
+ (end_caret1_x, end_caret1_y),
+ (end_caret2_x, end_caret2_y),
+ ) = sel.carets_location()
+
+ self.long_press_on_word(sel3, 3)
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+
+ # Drag the first caret down, which will across the second caret.
+ self.actions.flick(
+ body, caret1_x, caret1_y, end_caret1_x, end_caret1_y
+ ).perform()
+ self.assertEqual(
+ self.to_unix_line_ending(sel.selected_content.strip()), "3\nuser can select"
+ )
+
+ # The old second caret becomes the first caret. Drag it down again.
+ self.actions.flick(
+ body, caret2_x, caret2_y, end_caret2_x, end_caret2_y
+ ).perform()
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content.strip()), "this")
+
+ def test_drag_caret_to_beginning_of_a_line(self):
+ """Bug 1094056
+ Test caret visibility when caret is dragged to beginning of a line
+ """
+ self.open_test_html(self._multiplerange_html)
+ body = self.marionette.find_element(By.ID, "bd")
+ sel1 = self.marionette.find_element(By.ID, "sel1")
+ sel2 = self.marionette.find_element(By.ID, "sel2")
+
+ # Select the first word in the second line
+ self.long_press_on_word(sel2, 0)
+ sel = SelectionManager(body)
+ (
+ (start_caret_x, start_caret_y),
+ (end_caret_x, end_caret_y),
+ ) = sel.carets_location()
+
+ # Select target word in the first line
+ self.long_press_on_word(sel1, 2)
+
+ # Drag end caret to the beginning of the second line
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(
+ body, caret2_x, caret2_y, start_caret_x, start_caret_y
+ ).perform()
+
+ # Drag end caret back to the target word
+ self.actions.flick(
+ body, start_caret_x, start_caret_y, caret2_x, caret2_y
+ ).perform()
+
+ self.assertEqual(self.to_unix_line_ending(sel.selected_content), "select")
+
+ def test_select_word_inside_an_iframe(self):
+ """Bug 1088552
+ The scroll offset in iframe should be taken into consideration properly.
+ In this test, we scroll content in the iframe to the bottom to cause a
+ huge offset. If we use the right coordinate system, selection should
+ work. Otherwise, it would be hard to trigger select word.
+ """
+ self.open_test_html(self._iframe_html)
+ iframe = self.marionette.find_element(By.ID, "frame")
+
+ # switch to inner iframe and scroll to the bottom
+ self.marionette.switch_to_frame(iframe)
+ self.marionette.execute_script('document.getElementById("bd").scrollTop += 999')
+
+ # long press to select bottom text
+ body = self.marionette.find_element(By.ID, "bd")
+ sel = SelectionManager(body)
+ self._bottomtext = self.marionette.find_element(By.ID, "bottomtext")
+ self.long_press_on_location(self._bottomtext)
+
+ self.assertNotEqual(self.to_unix_line_ending(sel.selected_content), "")
+
+ def test_select_word_inside_an_unfocused_iframe(self):
+ """Bug 1306634: Test we can long press to select a word in an unfocused iframe."""
+ self.open_test_html(self._iframe_html)
+
+ el = self.marionette.find_element(By.ID, self._input_id)
+ sel = SelectionManager(el)
+
+ # First, we select the first word in the input of the parent document.
+ el_first_word = sel.content.split()[0] # first world is "ABC"
+ self.long_press_on_word(el, 0)
+ self.assertEqual(el_first_word, sel.selected_content)
+
+ # Then, we long press on the center of the iframe. It should select a
+ # word inside of the document, not the placehoder in the parent
+ # document.
+ iframe = self.marionette.find_element(By.ID, "frame")
+ self.long_press_on_location(iframe)
+ self.marionette.switch_to_frame(iframe)
+ body = self.marionette.find_element(By.ID, "bd")
+ sel = SelectionManager(body)
+ self.assertNotEqual("", sel.selected_content)
+
+ def test_carets_initialized_in_display_none(self):
+ """Test AccessibleCaretEventHub is properly initialized on a <html> with
+ display: none.
+
+ """
+ self.open_test_html(self._display_none_html)
+ html = self.marionette.find_element(By.ID, "html")
+ content = self.marionette.find_element(By.ID, "content")
+
+ # Remove 'display: none' from <html>
+ self.marionette.execute_script(
+ 'arguments[0].style.display = "unset";', script_args=[html]
+ )
+
+ # If AccessibleCaretEventHub is initialized successfully, select a word
+ # should work.
+ self._test_long_press_to_select_a_word(content)
+
+ @unittest.skip("Bug 1855083: High frequent intermittent.")
+ def test_long_press_to_select_when_partial_visible_word_is_selected(self):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, self._input_size_id)
+ sel = SelectionManager(el)
+
+ original_content = sel.content
+ words = original_content.split()
+
+ # We cannot use self.long_press_on_word() for the second long press
+ # on the first word because it has side effect that changes the
+ # cursor position. We need to save the location of the first word to
+ # be used later.
+ word0_x, word0_y = self.word_location(el, 0)
+
+ # Long press on the second word.
+ self.long_press_on_word(el, 1)
+ self.assertEqual(words[1], sel.selected_content)
+
+ # Long press on the first word.
+ self.long_press_on_location(el, word0_x, word0_y)
+ self.assertEqual(words[0], sel.selected_content)
+
+ # If the second caret is visible, it can be dragged to the position
+ # of the first caret. After that, selection will contain only the
+ # first character.
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+ self.actions.flick(el, caret2_x, caret2_y, caret1_x, caret1_y).perform()
+ self.assertEqual(words[0][0], sel.selected_content)
+
+ @parameterized(_input_id, el_id=_input_id)
+ @parameterized(_input_padding_id, el_id=_input_padding_id)
+ @parameterized(_textarea_one_line_id, el_id=_textarea_one_line_id)
+ @parameterized(_contenteditable_id, el_id=_contenteditable_id)
+ def test_carets_not_jump_when_dragging_to_editable_content_boundary(self, el_id):
+ self.open_test_html(self._selection_html)
+ el = self.marionette.find_element(By.ID, el_id)
+ sel = SelectionManager(el)
+ original_content = sel.content
+ words = original_content.split()
+ self.assertTrue(len(words) >= 3, "Expect at least three words in the content.")
+
+ # Goal: the selection is not changed after dragging the caret on the
+ # Y-axis.
+ target_content = words[1]
+
+ self.long_press_on_word(el, 1)
+ (caret1_x, caret1_y), (caret2_x, caret2_y) = sel.carets_location()
+
+ # Drag the first caret up by 40px.
+ self.actions.flick(el, caret1_x, caret1_y, caret1_x, caret1_y - 40).perform()
+ self.assertEqual(target_content, sel.selected_content)
+
+ # Drag the second caret down by 50px.
+ self.actions.flick(el, caret2_x, caret2_y, caret2_x, caret2_y + 50).perform()
+ self.assertEqual(target_content, sel.selected_content)
+
+ def test_carets_should_not_appear_when_long_pressing_svg_shapes(self):
+ self.open_test_html(self._svg_shapes_html)
+
+ rect = self.marionette.find_element(By.ID, "rect")
+ text = self.marionette.find_element(By.ID, "text")
+
+ sel = SelectionManager(text)
+ num_words_in_text = len(sel.content.split())
+
+ # Goal: the carets should not appear when long-pressing on the
+ # unselectable SVG rect.
+
+ # Get the position of the end of last word in text, i.e. the
+ # position of the second caret when selecting the last word.
+ self.long_press_on_word(text, num_words_in_text - 1)
+ (_, _), (x2, y2) = sel.carets_location()
+
+ # Long press to select the unselectable SVG rect.
+ self.long_press_on_location(rect)
+ (_, _), (x, y) = sel.carets_location()
+
+ # Drag the second caret to (x2, y2).
+ self.actions.flick(text, x, y, x2, y2).perform()
+
+ # If the carets should appear on the rect, the selection will be
+ # extended to cover all the words in text. Assert this should not
+ # happen.
+ self.assertNotEqual(sel.content, sel.selected_content.strip())
+
+ def test_select_word_scroll_then_drag_caret(self):
+ """Bug 1657256: Test select word, scroll page up , and then drag the second
+ caret down to cover "EEEEEE".
+
+ Note the selection should be extended to just cover "EEEEEE", not extend
+ to other lines below "EEEEEE".
+
+ """
+
+ self.open_test_html(self._iframe_scroll_html)
+ iframe = self.marionette.find_element(By.ID, "iframe")
+
+ # Switch to the inner iframe.
+ self.marionette.switch_to_frame(iframe)
+ body = self.marionette.find_element(By.ID, "bd")
+ sel = SelectionManager(body)
+
+ # Select "EEEEEE" to get the y position of the second caret. This is the
+ # y position we are going to drag the caret to.
+ content2 = self.marionette.find_element(By.ID, self._content2_id)
+ self.long_press_on_word(content2, 0)
+ (_, _), (x, y2) = sel.carets_location()
+
+ # Step 1: Select "DDDDDD".
+ content = self.marionette.find_element(By.ID, self._content_id)
+ self.long_press_on_word(content, 0)
+ (_, _), (_, y1) = sel.carets_location()
+
+ # The y-axis offset of the second caret needed to extend the selection.
+ y_offset = y2 - y1
+
+ # Step 2: Scroll the page upwards by 40px.
+ scroll_offset = 40
+ self.marionette.execute_script(
+ "document.documentElement.scrollTop += arguments[0]",
+ script_args=[scroll_offset],
+ )
+
+ # Step 3: Drag the second caret down.
+ self.actions.flick(
+ body, x, y1 - scroll_offset, x, y1 - scroll_offset + y_offset
+ ).perform()
+
+ self.assertEqual("DDDDDD EEEEEE", sel.selected_content)
+
+ @unittest.skip("Bug 1855083: High frequent intermittent.")
+ def test_carets_not_show_after_key_scroll_down_and_up(self):
+ self.open_test_html(self._key_scroll_html)
+ html = self.marionette.find_element(By.ID, "html")
+ sel = SelectionManager(html)
+
+ # Select "BBBBB" to get the position of the second caret. This is the
+ # position to which we are going to drag the caret in the step 3.
+ content2 = self.marionette.find_element(By.ID, self._content2_id)
+ self.long_press_on_word(content2, 0)
+ (_, _), (x2, y2) = sel.carets_location()
+
+ # Step 1: Select "AAAAA".
+ content = self.marionette.find_element(By.ID, self._content_id)
+ self.long_press_on_word(content, 0)
+ (_, _), (x1, y1) = sel.carets_location()
+
+ # Step 2: Scroll the page down and up.
+ #
+ # FIXME: The selection highlight on "AAAAA" often disappears after page
+ # up, which is the root cause of the intermittent. Longer pause between
+ # the page down and page up might help, but it's not a great solution.
+ self.actions.key_chain.send_keys(Keys.PAGE_DOWN).pause(1000).send_keys(
+ Keys.PAGE_UP
+ ).perform()
+ self.assertEqual("AAAAA", sel.selected_content)
+
+ # Step 3: The carets shouldn't show up after scrolling the page. We're
+ # attempting to drag the second caret down so that if the bug occurs, we
+ # can drag the second caret to extend the selection to "BBBBB".
+ self.actions.flick(html, x1, y1, x2, y2).perform()
+ self.assertNotEqual("AAAAA\nBBBBB", sel.selected_content)
diff --git a/layout/base/tests/mochitest.toml b/layout/base/tests/mochitest.toml
new file mode 100644
index 0000000000..d4b7932cfc
--- /dev/null
+++ b/layout/base/tests/mochitest.toml
@@ -0,0 +1,594 @@
+[DEFAULT]
+prefs = [
+ "formhelper.autozoom.force-disable.test-only=true",
+ "gfx.font_loader.delay=0",
+]
+support-files = [
+ "Ahem.ttf",
+ "file_bug842853.html",
+ "file_bug842853.sjs",
+ "selection-utils.js",
+ "!/gfx/layers/apz/test/mochitest/apz_test_utils.js",
+]
+
+["test_accessiblecaret_magnifier.html"]
+support-files = ["accessiblecaret_magnifier.html"]
+
+["test_after_paint_pref.html"]
+
+["test_border_radius_hit_testing.html"]
+support-files = ["border_radius_hit_testing_iframe.html"]
+
+["test_bug66619.html"]
+
+["test_bug93077-1.html"]
+
+["test_bug93077-2.html"]
+
+["test_bug93077-3.html"]
+
+["test_bug93077-4.html"]
+
+["test_bug93077-5.html"]
+
+["test_bug93077-6.html"]
+
+["test_bug114649.html"]
+
+["test_bug332655-1.html"]
+
+["test_bug332655-2.html"]
+
+["test_bug369950.html"]
+skip-if = ["true"] # Bug 492575
+support-files = ["bug369950-subframe.xml"]
+
+["test_bug370436.html"]
+
+["test_bug386575.xhtml"]
+
+["test_bug388019.html"]
+
+["test_bug394057.html"]
+skip-if = ["os == 'android'"] # Bug 1355817
+
+["test_bug399284.html"]
+
+["test_bug399951.html"]
+
+["test_bug404209.xhtml"]
+
+["test_bug416896.html"]
+
+["test_bug423523.html"]
+
+["test_bug435293-interaction.html"]
+
+["test_bug435293-scale.html"]
+
+["test_bug435293-skew.html"]
+
+["test_bug449781.html"]
+
+["test_bug450930.xhtml"]
+skip-if = ["true"] # bug 934301
+support-files = ["bug450930.xhtml"]
+
+["test_bug469170.html"]
+
+["test_bug471126.html"]
+
+["test_bug499538-1.html"]
+
+["test_bug514127.html"]
+
+["test_bug518777.html"]
+
+["test_bug548545.xhtml"]
+
+["test_bug558663.html"]
+support-files = ["bug558663.html"]
+
+["test_bug559499.html"]
+
+["test_bug569520.html"]
+
+["test_bug582181-1.html"]
+
+["test_bug582181-2.html"]
+
+["test_bug582771.html"]
+
+["test_bug583889.html"]
+support-files = [
+ "bug583889_inner1.html",
+ "bug583889_inner2.html",
+]
+
+["test_bug588174.html"]
+
+["test_bug603550.html"]
+fail-if = ["xorigin"]
+
+["test_bug607529.html"]
+support-files = [
+ "file_bug607529.html",
+ "file_bug607529-1.html",
+]
+
+["test_bug629838.html"]
+skip-if = ["os == 'android'"] # android: Requires plugin support
+
+["test_bug644768.html"]
+
+["test_bug646757.html"]
+
+["test_bug667512.html"]
+
+["test_bug677878.html"]
+
+["test_bug687297.html"]
+support-files = [
+ "bug687297_a.html",
+ "bug687297_b.html",
+ "bug687297_c.html",
+]
+
+["test_bug696020.html"]
+
+["test_bug718809.html"]
+
+["test_bug725426.html"]
+
+["test_bug731777.html"]
+
+["test_bug749186.html"]
+
+["test_bug761572.html"]
+
+["test_bug770106.html"]
+
+["test_bug842853-2.html"]
+
+["test_bug842853.html"]
+support-files = ["file_bug842853-frame.html"]
+
+["test_bug849219.html"]
+
+["test_bug851445.html"]
+skip-if = ["os == 'android'"] # Bug 1355821
+support-files = ["bug851445_helper.html"]
+
+["test_bug851485.html"]
+skip-if = ["os == 'android'"] # Bug 1355821
+
+["test_bug858459.html"]
+skip-if = ["os == 'android'"] # Bug 1355822
+
+["test_bug970964.html"]
+support-files = [
+ "bug970964_inner.html",
+ "bug970964_inner2.html",
+]
+
+["test_bug977003.html"]
+support-files = [
+ "bug977003_inner_1.html",
+ "bug977003_inner_2.html",
+ "bug977003_inner_3.html",
+ "bug977003_inner_4.html",
+ "bug977003_inner_5.html",
+ "bug977003_inner_6.html",
+]
+
+["test_bug990340.html"]
+
+["test_bug993936.html"]
+
+["test_bug1078327.html"]
+support-files = ["bug1078327_inner.html"]
+
+["test_bug1080360.html"]
+support-files = ["bug1080360_inner.html"]
+
+["test_bug1080361.html"]
+support-files = ["bug1080361_inner.html"]
+
+["test_bug1093686.html"]
+support-files = ["bug1093686_inner.html"]
+
+["test_bug1120705.html"]
+skip-if = [
+ "os == 'android'", # android does not have clickable scrollbars
+ "os == 'mac'", # mac does not have scrollbar down and up buttons
+ "os == 'linux' && os_version == '18.04'", # linux may or may not have scrollbar buttons depending on theme
+ "os == 'linux' && os_version == '22.04'", # linux may or may not have scrollbar buttons depending on theme
+]
+
+["test_bug1153130.html"]
+support-files = ["bug1153130_inner.html"]
+
+["test_bug1162990.html"]
+support-files = [
+ "bug1162990_inner_1.html",
+ "bug1162990_inner_2.html",
+]
+
+["test_bug1216483.html"]
+
+["test_bug1226904.html"]
+support-files = ["bug1226904.html"]
+
+["test_bug1246622.html"]
+
+["test_bug1278021.html"]
+
+["test_bug1448730.html"]
+support-files = ["bug1448730.html"]
+
+["test_bug1515822.html"]
+
+["test_bug1550869_video.html"]
+
+["test_bug1714640.html"]
+
+["test_bug1756118.html"]
+
+["test_caret_browsing_around_form_controls.html"]
+skip-if = ["os == 'android'"]
+
+["test_dynamic_toolbar_max_height.html"]
+support-files = ["file_dynamic_toolbar_max_height.html"]
+
+["test_emulateMedium.html"]
+
+["test_emulate_color_scheme.html"]
+
+["test_event_target_radius.html"]
+skip-if = ["xorigin"] # JavaScript error: resource://specialpowers/SpecialPowersChild.sys.mjs, line 73: SecurityError: Permission denied to access property "windowUtils" on cross-origin object
+
+["test_frame_reconstruction_body_table.html"]
+
+["test_frame_reconstruction_body_writing_mode.html"]
+
+["test_frame_reconstruction_for_column_span.html"]
+
+["test_frame_reconstruction_for_pseudo_elements.html"]
+
+["test_frame_reconstruction_for_svg_transforms.html"]
+
+["test_frame_reconstruction_scroll_restore.html"]
+
+["test_getBoxQuads_convertPointRectQuad.html"]
+support-files = [
+ "file_getBoxQuads_convertPointRectQuad_frame1.html",
+ "file_getBoxQuads_convertPointRectQuad_frame2.html",
+]
+
+["test_getClientRects_emptytext.html"]
+
+["test_mozPaintCount.html"]
+skip-if = ["os == 'android'"] # android: Requires plugin support
+
+["test_partialbg.html"]
+support-files = [
+ "sendimagenevercomplete.sjs",
+ "partial.png",
+]
+skip-if = [
+ "http3",
+ "http2",
+]
+
+["test_preserve3d_sorting_hit_testing.html"]
+support-files = ["preserve3d_sorting_hit_testing_iframe.html"]
+
+["test_preserve3d_sorting_hit_testing2.html"]
+support-files = ["preserve3d_sorting_hit_testing2_iframe.html"]
+
+["test_refreshDriver_hasPendingTick.html"]
+
+["test_reftests_with_caret.html"]
+skip-if = ["tsan"] # Bug 1612707
+support-files = [
+ "bug106855-1.html",
+ "bug106855-2.html",
+ "bug106855-1-ref.html",
+ "bug240933-1.html",
+ "bug240933-2.html",
+ "bug240933-1-ref.html",
+ "bug389321-1.html",
+ "bug389321-1-ref.html",
+ "bug389321-2.html",
+ "bug389321-2-ref.html",
+ "bug389321-3.html",
+ "bug389321-3-ref.html",
+ "bug482484.html",
+ "bug482484-ref.html",
+ "bug503399.html",
+ "bug503399-ref.html",
+ "bug512295-1.html",
+ "bug512295-1-ref.html",
+ "bug512295-2.html",
+ "bug512295-2-ref.html",
+ "bug585922.html",
+ "bug585922-ref.html",
+ "bug597519-1.html",
+ "bug597519-1-ref.html",
+ "bug602141-1.html",
+ "bug602141-1-ref.html",
+ "bug602141-2.html",
+ "bug602141-2-ref.html",
+ "bug602141-3.html",
+ "bug602141-3-ref.html",
+ "bug602141-4.html",
+ "bug602141-4-ref.html",
+ "bug612271-1.html",
+ "bug612271-2.html",
+ "bug612271-3.html",
+ "bug612271-ref.html",
+ "bug613433-1.html",
+ "bug613433-2.html",
+ "bug613433-3.html",
+ "bug613433-ref.html",
+ "bug613807-1.html",
+ "bug613807-1-ref.html",
+ "bug632215-1.html",
+ "bug632215-2.html",
+ "bug632215-ref.html",
+ "bug633044-1.html",
+ "bug633044-1-ref.html",
+ "bug634406-1.html",
+ "bug634406-1-ref.html",
+ "bug644428-1.html",
+ "bug644428-1-ref.html",
+ "bug646382-1.html",
+ "bug646382-1-ref.html",
+ "bug646382-2.html",
+ "bug646382-2-ref.html",
+ "bug664087-1.html",
+ "bug664087-1-ref.html",
+ "bug664087-2.html",
+ "bug664087-2-ref.html",
+ "bug682712-1.html",
+ "bug682712-1-ref.html",
+ "bug746993-1.html",
+ "bug746993-1-ref.html",
+ "bug923376.html",
+ "bug923376-ref.html",
+ "bug956530-1.html",
+ "bug956530-1-ref.html",
+ "bug966992-1.html",
+ "bug966992-1-ref.html",
+ "bug966992-2.html",
+ "bug966992-2-ref.html",
+ "bug989012-1.html",
+ "bug989012-1-ref.html",
+ "bug989012-2.html",
+ "bug989012-2-ref.html",
+ "bug989012-3.html",
+ "bug989012-3-ref.html",
+ "bug1007065-1.html",
+ "bug1007065-1-ref.html",
+ "bug1007067-1.html",
+ "bug1007067-1-ref.html",
+ "bug1061468.html",
+ "bug1061468-ref.html",
+ "bug1082486-1.html",
+ "bug1082486-1-ref.html",
+ "bug1082486-2.html",
+ "bug1082486-2-ref.html",
+ "bug1097242-1.html",
+ "bug1097242-1-ref.html",
+ "bug1109968-1-ref.html",
+ "bug1109968-1.html",
+ "bug1109968-2-ref.html",
+ "bug1109968-2.html",
+ "bug1123067-1.html",
+ "bug1123067-2.html",
+ "bug1123067-3.html",
+ "bug1123067-ref.html",
+ "bug1132768-1.html",
+ "bug1132768-1-ref.html",
+ "bug1237236-1.html",
+ "bug1237236-1-ref.html",
+ "bug1237236-2.html",
+ "bug1237236-2-ref.html",
+ "bug1258308-1.html",
+ "bug1258308-1-ref.html",
+ "bug1258308-2.html",
+ "bug1258308-2-ref.html",
+ "bug1259949-1.html",
+ "bug1259949-1-ref.html",
+ "bug1259949-2.html",
+ "bug1259949-2-ref.html",
+ "bug1263288.html",
+ "bug1263288-ref.html",
+ "bug1263357-1.html",
+ "bug1263357-1-ref.html",
+ "bug1263357-2.html",
+ "bug1263357-2-ref.html",
+ "bug1263357-3.html",
+ "bug1263357-3-ref.html",
+ "bug1263357-4.html",
+ "bug1263357-4-ref.html",
+ "bug1263357-5.html",
+ "bug1263357-5-ref.html",
+ "bug1354478-1.html",
+ "bug1354478-1-ref.html",
+ "bug1354478-2.html",
+ "bug1354478-2-ref.html",
+ "bug1354478-3.html",
+ "bug1354478-3-ref.html",
+ "bug1354478-4.html",
+ "bug1354478-4-ref.html",
+ "bug1354478-5.html",
+ "bug1354478-5-ref.html",
+ "bug1354478-6.html",
+ "bug1354478-6-ref.html",
+ "bug1359411.html",
+ "bug1359411-ref.html",
+ "bug1415416.html",
+ "bug1415416-ref.html",
+ "bug1423331-1.html",
+ "bug1423331-1-ref.html",
+ "bug1423331-2.html",
+ "bug1423331-2-ref.html",
+ "bug1423331-3.html",
+ "bug1423331-4.html",
+ "bug1484094-1.html",
+ "bug1484094-1-ref.html",
+ "bug1484094-2.html",
+ "bug1484094-2-ref.html",
+ "bug1496118.html",
+ "bug1496118-ref.html",
+ "bug1510942-1.html",
+ "bug1510942-1-ref.html",
+ "bug1510942-2.html",
+ "bug1510942-2-ref.html",
+ "bug1516963-1.html",
+ "bug1516963-1-ref.html",
+ "bug1516963-2.html",
+ "bug1516963-2-ref.html",
+ "bug1516963-3.html",
+ "bug1516963-3-ref.html",
+ "bug1516963-4.html",
+ "bug1516963-4-ref.html",
+ "bug1516963-5.html",
+ "bug1516963-5-ref.html",
+ "bug1516963-6.html",
+ "bug1516963-6-ref.html",
+ "bug1524266-1.html",
+ "bug1524266-1-ref.html",
+ "bug1524266-2.html",
+ "bug1524266-2-ref.html",
+ "bug1524266-3.html",
+ "bug1524266-4.html",
+ "bug1550869-1a.html",
+ "bug1550869-1b.html",
+ "bug1550869-1c.html",
+ "bug1550869-1-ref.html",
+ "bug1550869-2a.html",
+ "bug1550869-2b.html",
+ "bug1550869-2c.html",
+ "bug1550869-2d.html",
+ "bug1550869-2-ref.html",
+ "bug1591282-1.html",
+ "bug1591282-1-ref.html",
+ "bug1611661.html",
+ "bug1611661-ref.html",
+ "bug1634543-1.html",
+ "bug1634543-1-ref.html",
+ "bug1634543-2.html",
+ "bug1634543-3.html",
+ "bug1634543-4.html",
+ "bug1634743-1.html",
+ "bug1634743-1-ref.html",
+ "bug1637476-1.html",
+ "bug1637476-1-ref.html",
+ "bug1637476-2.html",
+ "bug1637476-2-ref.html",
+ "bug1637476-3.html",
+ "bug1637476-3-ref.html",
+ "bug1663475-1.html",
+ "bug1663475-1-ref.html",
+ "bug1663475-2.html",
+ "bug1663475-2-ref.html",
+ "bug1670531-1.html",
+ "bug1670531-2.html",
+ "bug1670531-3.html",
+ "bug1670531-3-ref.html",
+ "bug1670531-4.html",
+ "collapse-selection-into-editing-host-during-blur-of-input.html",
+ "collapse-selection-into-editing-host-during-blur-of-input-ref.html",
+ "image_rgrg-256x256.png",
+ "input-invalid-ref.html",
+ "input-maxlength-invalid-change.html",
+ "input-maxlength-ui-invalid-change.html",
+ "input-maxlength-ui-valid-change.html",
+ "input-maxlength-valid-before-change.html",
+ "input-maxlength-valid-change.html",
+ "input-minlength-invalid-change.html",
+ "input-minlength-ui-invalid-change.html",
+ "input-minlength-ui-valid-change.html",
+ "input-minlength-valid-before-change.html",
+ "input-minlength-valid-change.html",
+ "input-password-remask.html",
+ "input-password-remask-ref.html",
+ "input-password-RTL-input.html",
+ "input-password-RTL-input-ref.html",
+ "input-password-unmask.html",
+ "input-password-unmask-around-emoji.html",
+ "input-password-unmask-around-emoji-ref.html",
+ "input-password-unmask-ref.html",
+ "input-stoppropagation.html",
+ "input-stoppropagation-ref.html",
+ "input-valid-ref.html",
+ "interlinePosition-after-Selection-addRange.html",
+ "interlinePosition-after-Selection-addRange-ref.html",
+ "multi-range-script-select.html",
+ "multi-range-script-select-ref.html",
+ "multi-range-user-select.html",
+ "multi-range-user-select-ref.html",
+ "textarea-invalid-ref.html",
+ "textarea-maxlength-invalid-change.html",
+ "textarea-maxlength-ui-invalid-change.html",
+ "textarea-maxlength-ui-valid-change.html",
+ "textarea-maxlength-valid-before-change.html",
+ "textarea-maxlength-valid-change.html",
+ "textarea-minlength-invalid-change.html",
+ "textarea-minlength-ui-invalid-change.html",
+ "textarea-minlength-ui-valid-change.html",
+ "textarea-minlength-valid-before-change.html",
+ "textarea-minlength-valid-change.html",
+ "textarea-valid-ref.html",
+ "bug1506547-1.html",
+ "bug1506547-2.html",
+ "bug1506547-3.html",
+ "bug1506547-4.html",
+ "bug1506547-5.html",
+ "bug1506547-6.html",
+ "bug1506547-4-ref.html",
+ "bug1506547-5-ref.html",
+ "bug1518339-1.html",
+ "bug1518339-1-ref.html",
+ "bug1518339-2.html",
+ "bug1518339-2-ref.html",
+ "bug1529492-1.html",
+ "bug1529492-1-ref.html",
+ "chrome/blue-32x32.png",
+]
+
+["test_resize_flush.html"]
+support-files = ["resize_flush_iframe.html"]
+
+["test_scroll_event_ordering.html"]
+
+["test_scroll_selection_into_view.html"]
+skip-if = ["os == 'android'"] # Bug 1355844
+support-files = [
+ "scroll_selection_into_view_window.html",
+ "scroll_selection_into_view_window_frame.html",
+]
+
+["test_scroll_space_no_range_overflow_scroll.html"]
+
+["test_synthmousemove.html"]
+support-files = [
+ "helper_synthmousemove.html",
+ "file_synthmousemove.html",
+]
+
+["test_transformed_scrolling_repaints.html"]
+
+["test_transformed_scrolling_repaints_2.html"]
+skip-if = ["headless && os == 'mac'"] # Headless Bug 1414103
+
+["test_transformed_scrolling_repaints_3.html"]
+support-files = [
+ "transformed_scrolling_repaints_3_window.html",
+ "transformed_scrolling_repaints_3_window_frame.html",
+]
+
+["test_zoom_restore_bfcache.html"]
+support-files = ["file_zoom_restore_bfcache.html"]
diff --git a/layout/base/tests/multi-range-script-select-ref.html b/layout/base/tests/multi-range-script-select-ref.html
new file mode 100644
index 0000000000..509f5e4ae3
--- /dev/null
+++ b/layout/base/tests/multi-range-script-select-ref.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+
+var sel = window.getSelection();
+var e = document.querySelector('#select');
+function setupSelectionPrev3() {
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+function setupSelectionPrev2() {
+ addChildRanges([[0,150,0,160], [0,70,0,70]], e);
+ sel.extend(e.firstChild, 65); // to get eDirPrevious direction
+}
+function setupSelectionPrev1() {
+ addChildRanges([[0,160,0,160]], e);
+ sel.extend(e.firstChild, 150); // to get eDirPrevious direction
+}
+
+function setupSelectionNext3() {
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+function setupSelectionNext2() {
+ addChildRanges([[0,10,0,15], [0,65,0,70]], e);
+}
+function setupSelectionNext2b() {
+ addChildRanges([[0,15,0,80], [0,150,0,160]], e);
+}
+function setupSelectionNext1() {
+ addChildRanges([[0,10,0,15]], e);
+}
+function setupSelectionNext1b() {
+ addChildRanges([[0,15,0,170]], e);
+}
+function setupSelectionNext1c() {
+ addChildRanges([[0,150,0,160]], e);
+}
+
+function runTest() {
+ var hash = window.location.hash
+ var op = hash.substring(6,8);
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ setupSelectionPrev3();
+ if (op == "SL") {
+ sel.extend(e.firstChild, 8);
+ } else if (op == "SR") {
+ sel.extend(e.firstChild, 12);
+ } else if (op == "AD") {
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#prev2") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#prev3") {
+ if (op == "S_") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 20);
+ } else if (op == "SA") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 20);
+ }
+ } else if (test == "#prev4") {
+ addChildRanges([[0,67,0,70], [0,150,0,160], [0,15,0,67]], e);
+ } else if (test == "#prev5") {
+ if (op == "S_") {
+ setupSelectionNext2b();
+ } else if (op == "SA") {
+ setupSelectionNext2b();
+ }
+ } else if (test == "#prev6") {
+ addChildRanges([[0,152,0,160], [0,15,0,152]], e);
+ } else if (test == "#prev7") {
+ if (op == "AD") {
+ setupSelectionPrev3();
+ addChildRanges([[0,168,0,170]], e);
+ } else if (op == "S_") {
+ setupSelectionNext1b();
+ } else if (op == "SA") {
+ setupSelectionNext1b();
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (op == "SL") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 158);
+ } else if (op == "SR") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 162);
+ } else if (op == "AD") {
+ setupSelectionNext3();
+ addChildRanges([[0,1,0,2]], e);
+ } else if (op == "S_") {
+ setupSelectionNext1c();
+ sel.extend(e.firstChild, 1);
+ } else if (op == "SA") {
+ setupSelectionNext1c();
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#next2") {
+ addChildRanges([[0,10,0,13], [0,150,0,151]], e);
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#next3") {
+ if (op == "S_") {
+ addChildRanges([[0,10,0,15], [0,150,0,151]], e);
+ sel.extend(e.firstChild, 20);
+ } else if (op == "SA") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 20);
+ }
+ } else if (test == "#next4") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 67);
+ } else if (test == "#next5") {
+ if (op == "S_") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 80);
+ } else if (op == "SA") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 80);
+ }
+ } else if (test == "#next6") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 152);
+ } else if (test == "#next7") {
+ setupSelectionNext3();
+ if (op == "AD") {
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ sel.extend(e.firstChild, 170);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-script-select.html b/layout/base/tests/multi-range-script-select.html
new file mode 100644
index 0000000000..e5ad7d2e77
--- /dev/null
+++ b/layout/base/tests/multi-range-script-select.html
@@ -0,0 +1,185 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+window.info = parent.info;
+window.is = parent.is;
+window.isnot = parent.isnot;
+window.ok = parent.ok;
+
+function setupPrevSelection() {
+ var sel = window.getSelection();
+ var e = document.querySelector('#select');
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+
+function setupNextSelection() {
+ var sel = window.getSelection();
+ var e = document.querySelector('#select');
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+
+var ops = {
+ S_ : shiftClick,
+ SA : shiftAccelClick,
+ AD : accelDragSelect,
+ SL : keyLeft,
+ SR : keyRight
+}
+
+function runTest() {
+ var e = document.querySelector('#select');
+ var hash = window.location.hash
+ if (hash.substring(0,5)=="#prev")
+ setupPrevSelection();
+ else
+ setupNextSelection();
+ var op = hash.substring(6,8);
+ var action = ops[op];
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,8,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,12,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,15], [0,65,0,70], [0,150,0,160]], e);
+ }
+ } else if (test == "#prev2") {
+ action(e, 260);
+ checkRanges([[0,13,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev3") {
+ action(e, 400);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,20], [0,65,0,70], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,20], [0,65,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev4") {
+ action(e, 180, 65);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,67], [0,67,0,70], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,67], [0,67,0,70], [0,150,0,160]], e);
+ } else if (test == "#prev5") {
+ action(e, 440, 65);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,80], [0,150,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,80], [0,150,0,160]], e);
+ } else if (test == "#prev6") {
+ action(e, 140, 125);
+ if (action == shiftClick)
+ checkRanges([[0,15,0,152], [0,152,0,160]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,15,0,152], [0,152,0,160]], e);
+ } else if (test == "#prev7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160], [0,168,0,170]], e);
+ } else if (action == shiftClick) {
+ action(e, 500, 125);
+ checkRanges([[0,15,0,170]], e);
+ } else if (action == shiftAccelClick) {
+ action(e, 500, 125);
+ checkRanges([[0,15,0,170]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,158]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,162]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,150]], e);
+ }
+ } else if (test == "#next2") {
+ action(e, 260);
+ checkRanges([[0,10,0,13], [0,13,0,150]], e);
+ } else if (test == "#next3") {
+ action(e, 400);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,20,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,20,0,150]], e);
+ } else if (test == "#next4") {
+ action(e, 180, 65);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,67], [0,67,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,67], [0,67,0,150]], e);
+ } else if (test == "#next5") {
+ action(e, 440, 65);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,80,0,150]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,80,0,150]], e);
+ } else if (test == "#next6") {
+ action(e, 140, 125);
+ if (action == shiftClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,152]], e);
+ else if (action == shiftAccelClick)
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,152]], e);
+ } else if (test == "#next7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160], [0,168,0,170]], e);
+ } else if (action == shiftClick) {
+ action(e, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,170]], e);
+ } else if (action == shiftAccelClick) {
+ action(e, 500, 125);
+ checkRanges([[0,10,0,15], [0,65,0,70], [0,150,0,170]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(runTest);
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-user-select-ref.html b/layout/base/tests/multi-range-user-select-ref.html
new file mode 100644
index 0000000000..00d7d41269
--- /dev/null
+++ b/layout/base/tests/multi-range-user-select-ref.html
@@ -0,0 +1,166 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-ak7e-11e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae4-ci5c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a96319c8-ad7d-11e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+
+var sel = window.getSelection();
+var e = document.querySelector('#select');
+function setupSelectionPrev3() {
+ addChildRanges([[0,150,0,160], [0,65,0,70], [0,15,0,15]], e);
+ sel.extend(e.firstChild, 10); // to get eDirPrevious direction
+}
+function setupSelectionPrev2() {
+ addChildRanges([[0,150,0,160], [0,70,0,70]], e);
+ sel.extend(e.firstChild, 65); // to get eDirPrevious direction
+}
+function setupSelectionPrev1() {
+ addChildRanges([[0,160,0,160]], e);
+ sel.extend(e.firstChild, 150); // to get eDirPrevious direction
+}
+
+function setupSelectionNext3() {
+ addChildRanges([[0,10,0,15], [0,65,0,70], [0,150,0,160]], e);
+}
+function setupSelectionNext2() {
+ addChildRanges([[0,10,0,15], [0,65,0,70]], e);
+}
+function setupSelectionNext2b() {
+ addChildRanges([[0,15,0,80], [0,150,0,160]], e);
+}
+function setupSelectionNext1() {
+ addChildRanges([[0,10,0,15]], e);
+}
+function setupSelectionNext1b() {
+ addChildRanges([[0,15,0,170]], e);
+}
+function setupSelectionNext1c() {
+ addChildRanges([[0,150,0,160]], e);
+}
+
+function runTest() {
+ sel = window.getSelection();
+ sel.removeAllRanges();
+ document.body.offsetHeight;
+ var hash = window.location.hash
+ var op = hash.substring(6,8);
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ setupSelectionPrev3();
+ if (op == "SL") {
+ sel.extend(e.firstChild, 8);
+ } else if (op == "SR") {
+ sel.extend(e.firstChild, 12);
+ } else if (op == "AD") {
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#prev2") {
+ setupSelectionPrev3();
+ sel.extend(e.firstChild, 14); // now eDirNext
+ sel.extend(e.firstChild, 13); // now eDirPrevious again
+ } else if (test == "#prev3") {
+ setupSelectionPrev2();
+ sel.extend(e.firstChild, 20);
+ } else if (test == "#prev4") {
+ setupSelectionPrev2();
+ sel.extend(e.firstChild, 68); // now eDirNext
+ sel.extend(e.firstChild, 67); // now eDirPrevious again
+ } else if (test == "#prev5") {
+ setupSelectionPrev1();
+ sel.extend(e.firstChild, 80);
+ } else if (test == "#prev6") {
+ setupSelectionPrev1();
+ sel.extend(e.firstChild, 153); // now eDirNext
+ sel.extend(e.firstChild, 152); // now eDirPrevious again
+ } else if (test == "#prev7") {
+ if (op == "AD") {
+ setupSelectionPrev3();
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ addChildRanges([[0,160,0,170]], e);
+ }
+ } else if (test == "#prev8") {
+ if (op == "AD") {
+ addChildRanges([[0,150,0,155], [0,68,0,70]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (op == "SL") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 158);
+ } else if (op == "SR") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 162);
+ } else if (op == "AD") {
+ setupSelectionNext3();
+ addChildRanges([[0,1,0,2]], e);
+ } else {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 1);
+ }
+ } else if (test == "#next2") {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 13);
+ } else if (test == "#next3") {
+ setupSelectionNext1();
+ sel.extend(e.firstChild, 20);
+ } else if (test == "#next4") {
+ setupSelectionNext2();
+ sel.extend(e.firstChild, 67);
+ } else if (test == "#next5") {
+ setupSelectionNext2();
+ sel.extend(e.firstChild, 80);
+ } else if (test == "#next6") {
+ setupSelectionNext3();
+ sel.extend(e.firstChild, 152);
+ } else if (test == "#next7") {
+ setupSelectionNext3();
+ if (op == "AD") {
+ addChildRanges([[0,168,0,170]], e);
+ } else {
+ sel.extend(e.firstChild, 170);
+ }
+ } else if (test == "#next8") {
+ if (op == "AD") {
+ addChildRanges([[0,68,0,70], [0,150,0,155]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(function(){setTimeout(runTest,0)});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/multi-range-user-select.html b/layout/base/tests/multi-range-user-select.html
new file mode 100644
index 0000000000..f23e0627a6
--- /dev/null
+++ b/layout/base/tests/multi-range-user-select.html
@@ -0,0 +1,223 @@
+<!DOCTYPE HTML>
+<html class="reftest-wait"><head>
+ <meta charset="utf-8">
+ <title>Testcase #1 for bug 1129078</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="selection-utils.js"></script>
+
+<style type="text/css">
+@font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+}
+html,body { margin:0; padding: 0; }
+body,pre { font-family: Ahem; font-size: 20px; }
+span { -moz-user-select:none; }
+x { -moz-user-select:text; }
+</style>
+</head>
+<body>
+
+<pre id="select">
+2af45494-a<x>k7e-1</x><span id="span2">1e4-a0c6-a7e7
+38222880-bj6d-11e4-8064-fb7b
+3d649ae</span><x>4-ci5</x><span id="span3">c-11e4-995d-17b2
+434351bc-dh4b-11e4-9971-4fc8
+4dc0e0b4-eg4a-11e4-8c28-5319
+a9631</span><x>9c8-ad7d-1</x>1e4-b312-039c
+</pre>
+
+<pre id="log" style="border:1px solid green"></pre>
+
+<script>
+window.info = parent.info;
+window.is = parent.is;
+window.isnot = parent.isnot;
+window.ok = parent.ok;
+
+var sel = window.getSelection();
+
+function enableSelection(id) {
+ var span = document.getElementById(id);
+ span.style.MozUserSelect = 'text';
+}
+
+function setupPrevSelection() {
+ var e = document.querySelector('#select');
+ dragSelectPoints(e, 300, 125, 200, 5);
+}
+
+function setupNextSelection() {
+ var e = document.querySelector('#select');
+ dragSelectPoints(e, 199, 5, 300, 125);
+}
+
+var ops = {
+ S_ : shiftClick,
+ SA : shiftAccelClick,
+ AD : accelDragSelect,
+ SL : keyLeft,
+ SR : keyRight
+}
+
+function runTest() {
+ sel = window.getSelection();
+ sel.removeAllRanges();
+ document.body.offsetHeight;
+ var e = document.querySelector('#select');
+ var hash = window.location.hash
+ if (hash.substring(0,5)=="#prev")
+ setupPrevSelection();
+ else
+ setupNextSelection();
+ var op = hash.substring(6,8);
+ var action = ops[op];
+ var test = hash.substring(0,6);
+ if (hash.substring(0,5) == "#prev") {
+ if (test == "#prev1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,8,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[e.childNodes[1].firstChild,2,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [e.childNodes[1].firstChild,0,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,-1,2], [3,0,-1,4], [5,0,6,0]], e);
+ }
+ } else if (test == "#prev2") {
+ action(e, 260);
+ checkRangeCount(3, e);
+ checkRange(0, [0,3,-2,2], e.childNodes[1]);
+ checkRange(1, [3,0,-1,4], e);
+ checkRange(2, [5,0,6,0], e);
+ } else if (test == "#prev3") {
+ enableSelection('span2');
+ action(e, 400);
+ checkRangeCount(2, e);
+ checkRange(0, [0,5,-2,4], e.childNodes[2]);
+ checkRange(1, [5,0,6,0], e);
+ } else if (test == "#prev4") {
+ action(e, 180, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,2,-2,4], e.childNodes[3]);
+ checkRange(1, [5,0,6,0], e);
+ } else if (test == "#prev5") {
+ enableSelection('span3');
+ action(e, 440, 65);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[4].firstChild,10,e.childNodes[6],0], e);
+ } else if (test == "#prev6") {
+ action(e, 140, 125);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[5].firstChild,2,e.childNodes[6],0], e);
+ } else if (test == "#prev7") {
+ if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[e.childNodes[1].firstChild,0,-1,2], [3,0,-1,4], [5,0,6,0], [6,8,6,10]], e);
+ } else {
+ action(e, 500, 125);
+ checkRanges([[6,0,6,10]], e);
+ }
+ } else if (test == "#prev8") {
+ if (action == accelDragSelect) {
+ sel.removeAllRanges();
+ var e = document.querySelector('#select');
+ synthesizeMouse(e, 200, 125, {type: "mousedown", accelKey: true});
+ synthesizeMouse(e, 200, 120, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 100, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 80, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 210, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 60, {type: "mouseup", accelKey: true});
+ var x3t = e.childNodes[3].firstChild;
+ var x5 = e.childNodes[5];
+ checkRanges([[x3t,3,-1,4], [x5,0,x5.firstChild,5]], e);
+ }
+ }
+ } else {
+ if (test == "#next1") {
+ if (action == keyLeft) {
+ keyLeft({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,8]], e);
+ } else if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,6,2]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 30, 50);
+ checkRanges([[0,1,0,2], [0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,10]], e);
+ } else {
+ action(e, 30);
+ checkRanges([[0,1,0,10]], e);
+ }
+ } else if (test == "#next2") {
+ action(e, 260);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[0],10,e.childNodes[1].firstChild,3], e);
+ } else if (test == "#next3") {
+ enableSelection('span2');
+ action(e, 400);
+ checkRangeCount(1, e);
+ checkRangePoints(0, [e.childNodes[0],10,e.childNodes[2].firstChild,5], e);
+ } else if (test == "#next4") {
+ action(e, 180, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [-1,0,0,2], e.childNodes[3]);
+ } else if (test == "#next5") {
+ enableSelection('span3');
+ action(e, 440, 65);
+ checkRangeCount(2, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRangePoints(1, [e.childNodes[3],0,e.childNodes[4].firstChild,10], e);
+ } else if (test == "#next6") {
+ action(e, 140, 125);
+ checkRangeCount(3, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [3,0,-1,4], e);
+ checkRange(2, [-1,0,0,2], e.childNodes[5]);
+ } else if (test == "#next7") {
+ if (action == keyRight) {
+ keyRight({shiftKey:true}, 2)
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,6,2]], e);
+ } else if (action == accelDragSelect) {
+ accelDragSelect(e, 460, 500, 125);
+ checkRanges([[0,10,-1,2], [3,0,-1,4], [5,0,e.childNodes[5].firstChild,10], [6,8,6,10]], e);
+ } else {
+ action(e, 500, 125);
+ checkRangeCount(3, e);
+ checkRange(0, [0,10,-1,2], e);
+ checkRange(1, [3,0,-1,4], e);
+ checkRangePoints(2, [e.childNodes[5],0,e.childNodes[6],10], e);
+ }
+ } else if (test == "#next8") {
+ if (action == accelDragSelect) {
+ sel.removeAllRanges();
+ var e = document.querySelector('#select');
+ synthesizeMouse(e, 200, 60, {type: "mousedown", accelKey: true});
+ synthesizeMouse(e, 180, 60, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 80, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 100, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 120, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 190, 125, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 125, {type: "mousemove", accelKey: true});
+ synthesizeMouse(e, 200, 125, {type: "mouseup", accelKey: true});
+ var x3t = e.childNodes[3].firstChild;
+ var x5 = e.childNodes[5];
+ checkRanges([[x3t,3,-1,4], [x5,0,x5.firstChild,5]], e);
+ }
+ }
+ }
+ document.documentElement.removeAttribute("class");
+}
+
+SimpleTest.waitForFocus(function(){setTimeout(runTest,0)});
+
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/partial.png b/layout/base/tests/partial.png
new file mode 100644
index 0000000000..6369d58600
--- /dev/null
+++ b/layout/base/tests/partial.png
Binary files differ
diff --git a/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html b/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html
new file mode 100644
index 0000000000..6ca45418d0
--- /dev/null
+++ b/layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<style>
+body {
+ background: #333;
+ overflow: hidden;
+}
+
+div {
+ margin: 0;
+ padding: 0;
+ transform-style: preserve-3d;
+ position: absolute;
+}
+
+#container {
+ font-family: UnifrakturMaguntia;
+ width: 350px;
+ height: 70%;
+ max-height: 500px;
+ perspective: 5000px;
+ transform: translate(-50%, -50%) rotateY(20deg);
+}
+
+#container p {
+ padding: 0 5px 0 5px;
+}
+
+#container hr {
+ margin: 0 20px 0 20px;
+}
+
+#content {
+ overflow: hidden scroll;
+ height: 100%;
+ background: #fefee0;
+}
+
+
+#lorem {
+ font-size: 7em;
+ float: left;
+ color: red;
+ border: 1px solid black;
+ margin-right: 5px;
+}
+
+#tree {
+ float: right;
+ width: 10em;
+ height: 10em;
+ border: 1px solid black;
+ margin: 0 5px 0 2px;
+}
+</style>
+</head>
+<body>
+ <div id="container">
+ <div id="content">
+ <p>
+ <span id="lorem">L</span>orem ipsum dolor sit amet, consectetur adipiscing elit. Integer sagittis nisi urna, a ultrices est facilisis a. Morbi porttitor vulputate odio, eu lacinia nisi. Suspendisse felis sapien, facilisis nec ex in, blandit tincidunt tellus. Sed at commodo nunc. In nibh lectus, facilisis nec magna nec, bibendum egestas nunc. Nam varius lorem in fringilla cursus. Integer dignissim, lectus vitae sodales molestie, libero purus malesuada arcu, vitae facilisis nunc dolor non mi. In nunc tortor, tempor non pharetra vitae, mattis a purus. Nulla rhoncus vitae metus vel ornare. Nunc augue dui, suscipit ac urna vel, consectetur volutpat ipsum. Nunc ac nulla ut enim laoreet placerat. Sed luctus aliquam purus, sollicitudin blandit dui blandit id. Aenean venenatis risus dolor, at viverra urna aliquam non. Morbi sit amet pellentesque justo, eget viverra augue.
+ </p>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Praesent posuere ultricies orci sit amet lacinia. Suspendisse lacinia scelerisque risus, sodales egestas turpis cursus sed. Proin sed mollis mauris, vitae ultricies nibh. Nulla bibendum leo a mauris luctus, sit amet iaculis arcu blandit. Etiam pulvinar, odio et rutrum egestas, elit mi maximus ex, id elementum est tortor id turpis. Duis rhoncus et lorem vel maximus. Aenean at justo sagittis, aliquet eros eget, iaculis magna. Nam non orci congue, dapibus dui eget, sagittis nisl. Phasellus venenatis id est et tempor. Aenean condimentum tristique nibh sit amet varius. Vestibulum et lectus quis eros dapibus consectetur nec auctor dolor. Sed euismod eu felis aliquam fermentum. Donec lacinia fringilla erat, at eleifend velit tempus at.
+ </p>
+ <hr>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Cras justo turpis, vulputate eget venenatis sit amet, bibendum quis dolor. Cras at interdum libero. Quisque convallis rutrum magna in ultrices. Donec ut magna dolor. Mauris pulvinar ut sapien a posuere. Sed nisi elit, tincidunt vitae magna eu, dapibus suscipit purus. Maecenas tincidunt mollis eros et dictum. Duis est nulla, rhoncus tincidunt velit at, venenatis elementum velit. Phasellus lobortis sem tellus, id sodales quam dignissim nec. Phasellus pulvinar metus ex, nec gravida nunc elementum vel. Ut mattis varius fringilla. Phasellus imperdiet sit amet risus a elementum. Donec pulvinar ante sit amet massa blandit ullamcorper. Donec vitae malesuada nisl, et laoreet sem.
+ </p>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Suspendisse bibendum elit blandit arcu vulputate, nec hendrerit dui vehicula. Vestibulum porta finibus odio vitae maximus. Duis in vulputate risus. Donec mattis turpis ex, vitae semper sem ultrices eu. Aliquam in ex blandit erat ultrices sollicitudin. Vestibulum porta nisl in porttitor rutrum. Integer consectetur porttitor ligula facilisis malesuada. Proin placerat enim sed lacus commodo mollis nec eu arcu. In hac habitasse platea dictumst. Curabitur luctus est risus, sit amet fringilla nunc condimentum vel. Integer mauris lorem, molestie ut nisl sit amet, pellentesque mollis quam. Aliquam posuere purus non nisi molestie semper.
+ </p>
+ <hr>
+ <p>&nbsp;&nbsp;&nbsp;&nbsp;
+ Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris facilisis nisi diam, eu pulvinar ex sollicitudin sed. Maecenas sed eros id quam suscipit ultricies ut tincidunt quam. Donec iaculis, justo at fringilla laoreet, quam sem dapibus urna, ut eleifend odio eros et ligula. Proin urna ante, condimentum vitae sollicitudin sit amet, egestas ac nunc. Aenean sapien velit, porta a eros quis, iaculis dignissim felis. Suspendisse mollis vulputate metus vel interdum. Aliquam hendrerit elementum erat, sit amet commodo velit suscipit et. Sed semper sem at mauris rhoncus, id efficitur arcu molestie. Nam feugiat lorem pretium, consectetur felis et, fringilla dolor. Nunc dui velit, elementum non hendrerit nec, sagittis vitae odio. Curabitur nec leo tincidunt, pellentesque metus at, condimentum risus.
+ </p>
+ </div>
+ </div>
+</body>
+
+<script type="application/javascript">
+ window.onload = function() {
+ opener.child_opened(document);
+ };
+</script>
+
+</html>
diff --git a/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html b/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
new file mode 100644
index 0000000000..d375e4b97a
--- /dev/null
+++ b/layout/base/tests/preserve3d_sorting_hit_testing_iframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<title>preserve-3d hit testing</title>
+<style>
+ #parent {
+ transform-style: preserve-3d;
+ }
+
+ #big {
+ width: 1000px;
+ height: 1000px;
+ background-color: #995C7F;
+ transform: translate3d(0px, 0px, 350px) rotatey(45deg);
+ }
+
+ #small {
+ width: 100px;
+ height: 100px;
+ background-color: #835A99;
+ transform: translate3d(600px, 200px, 350px);
+ }
+</style>
+<body>
+ <div id="stage">
+ <div id="parent">
+ <div id="small"></div>
+ <div id="big"></div>
+ </div>
+ </div>
diff --git a/layout/base/tests/resize_flush_iframe.html b/layout/base/tests/resize_flush_iframe.html
new file mode 100644
index 0000000000..37555a514a
--- /dev/null
+++ b/layout/base/tests/resize_flush_iframe.html
@@ -0,0 +1,17 @@
+<html>
+<head>
+ <style>
+ body {
+ background-color: red;
+ }
+
+ @media (min-width: 250px) {
+ body {
+ background-color: green;
+ }
+ }
+ </style>
+</head>
+<body>
+</body>
+</html>
diff --git a/layout/base/tests/scroll_into_view_in_child.html b/layout/base/tests/scroll_into_view_in_child.html
new file mode 100644
index 0000000000..0e8dd13dda
--- /dev/null
+++ b/layout/base/tests/scroll_into_view_in_child.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<div id="target">can you see me?</div>
+<script>
+window.onload = () => {
+ target.scrollIntoView();
+};
+</script>
diff --git a/layout/base/tests/scroll_selection_into_view_window.html b/layout/base/tests/scroll_selection_into_view_window.html
new file mode 100644
index 0000000000..cbb474a3b6
--- /dev/null
+++ b/layout/base/tests/scroll_selection_into_view_window.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scrolling selection into view</title>
+</head>
+<body onload="opener.doTest();">
+
+<div id="c1" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:0;">
+ <div style="height:400px;"></div>
+ <div><span id="target1"
+ style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c2" style="overflow:hidden; width:200px; height:200px; position:absolute; top:200px; left:200px;">
+ <div style="height:400px;"></div>
+ <div><span id="target2"
+ style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c3" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:400px;">
+ <div style="height:400px;"></div>
+ <div><span id="target3"
+ style="display:inline-block; vertical-align:top; height:300px;"></span>
+ </div>
+ <div style="height:400px;"></div>
+</div>
+<div id="c4" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:200px; left:600px;">
+ <iframe id="target4" style="border:none; width:100%; height:1100px; display:block;"
+ src="scroll_selection_into_view_window_frame.html"></iframe>
+</div>
+<div id="c5" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:0;">
+ <div style="transform:translateY(400px); transform:translateY(400px)">
+ <span id="target5" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+<div id="c6" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:200px;">
+ <div style="height:200px"></div>
+ <div style="height:100px; transform:scale(2); transform:scale(2)">
+ <span id="target6" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+<div id="c7" style="overflow-y:scroll; width:200px; height:200px; position:absolute; top:400px; left:400px;">
+ <div style="overflow:auto; height:200px; transform:translateY(400px); transform:translateY(400px)">
+ <div style="height:200px;"></div>
+ <div>
+ <span id="target7" style="display:inline-block; vertical-align:top; height:20px;"></span>
+ </div>
+ <div style="height:800px;"></div>
+ </div>
+ <div style="height:800px;"></div>
+</div>
+
+</body>
+
+</html>
diff --git a/layout/base/tests/scroll_selection_into_view_window_frame.html b/layout/base/tests/scroll_selection_into_view_window_frame.html
new file mode 100644
index 0000000000..e5bb294021
--- /dev/null
+++ b/layout/base/tests/scroll_selection_into_view_window_frame.html
@@ -0,0 +1,6 @@
+<body style='margin:0; overflow:hidden;'>
+<div style='height:400px;'></div>
+<div><span id='target4'
+ style='display:inline-block; vertical-align:top; height:300px;'></span>
+</div>
+<div style='height:400px;'></div>
diff --git a/layout/base/tests/selection-utils.js b/layout/base/tests/selection-utils.js
new file mode 100644
index 0000000000..67db5139ca
--- /dev/null
+++ b/layout/base/tests/selection-utils.js
@@ -0,0 +1,167 @@
+// -*- Mode: Javascript; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*-
+// vim: set ts=2 sw=2 et tw=78:
+function clearSelection(w) {
+ var sel = (w ? w : window).getSelection();
+ sel.removeAllRanges();
+}
+
+function getNode(e, index) {
+ if (!(typeof index === "number")) {
+ return index;
+ }
+ if (index >= 0) {
+ return e.childNodes[index];
+ }
+ while (++index != 0) {
+ e = e.parentNode;
+ }
+ return e;
+}
+
+function dragSelectPointsWithData() {
+ var event = arguments[0];
+ var e = arguments[1];
+ var x1 = arguments[2];
+ var y1 = arguments[3];
+ var x2 = arguments[4];
+ var y2 = arguments[5];
+ dir = x2 > x1 ? 1 : -1;
+ event.type = "mousedown";
+ synthesizeMouse(e, x1, y1, event);
+ event.type = "mousemove";
+ synthesizeMouse(e, x1 + dir, y1, event);
+ for (var i = 6; i < arguments.length; ++i) {
+ synthesizeMouse(e, arguments[i], y1, event);
+ }
+ synthesizeMouse(e, x2 - dir, y2, event);
+ event.type = "mouseup";
+ synthesizeMouse(e, x2, y2, event);
+}
+
+function dragSelectPoints() {
+ var args = Array.prototype.slice.call(arguments);
+ dragSelectPointsWithData.apply(this, [{}].concat(args));
+}
+
+function dragSelect() {
+ var args = Array.prototype.slice.call(arguments);
+ var e = args.shift();
+ var x1 = args.shift();
+ var x2 = args.shift();
+ dragSelectPointsWithData.apply(this, [{}, e, x1, 5, x2, 5].concat(args));
+}
+
+function accelDragSelect() {
+ var args = Array.prototype.slice.call(arguments);
+ var e = args.shift();
+ var x1 = args.shift();
+ var x2 = args.shift();
+ var y = args.length != 0 ? args.shift() : 5;
+ dragSelectPointsWithData.apply(
+ this,
+ [{ accelKey: true }, e, x1, y, x2, y].concat(args)
+ );
+}
+
+function shiftClick(e, x, y) {
+ function pos(p) {
+ return typeof p === "undefined" ? 5 : p;
+ }
+ synthesizeMouse(e, pos(x), pos(y), { shiftKey: true });
+}
+
+function keyRepeat(key, data, repeat) {
+ while (repeat-- > 0) {
+ synthesizeKey(key, data);
+ }
+}
+
+function keyRight(data) {
+ var repeat = arguments.length > 1 ? arguments[1] : 1;
+ keyRepeat("VK_RIGHT", data, repeat);
+}
+
+function keyLeft(data) {
+ var repeat = arguments.length > 1 ? arguments[1] : 1;
+ keyRepeat("VK_LEFT", data, repeat);
+}
+
+function shiftAccelClick(e, x, y) {
+ function pos(p) {
+ return typeof p === "undefined" ? 5 : p;
+ }
+ synthesizeMouse(e, pos(x), pos(y), { shiftKey: true, accelKey: true });
+}
+
+function accelClick(e, x, y) {
+ function pos(p) {
+ return typeof p === "undefined" ? 5 : p;
+ }
+ synthesizeMouse(e, pos(x), pos(y), { accelKey: true });
+}
+
+function addChildRanges(arr, e) {
+ var sel = window.getSelection();
+ for (i = 0; i < arr.length; ++i) {
+ var data = arr[i];
+ var r = new Range();
+ r.setStart(getNode(e, data[0]), data[1]);
+ r.setEnd(getNode(e, data[2]), data[3]);
+ sel.addRange(r);
+ }
+}
+
+function checkText(text, e) {
+ var sel = window.getSelection();
+ is(sel.toString(), text, e.id + ": selected text");
+}
+
+function checkRangeText(text, index) {
+ var r = window.getSelection().getRangeAt(index);
+ is(r.toString(), text, e.id + ": range[" + index + "].toString()");
+}
+
+function checkRangeCount(n, e) {
+ var sel = window.getSelection();
+ is(sel.rangeCount, n, e.id + ": Selection range count");
+}
+
+function checkRangePoints(i, expected, e) {
+ var sel = window.getSelection();
+ if (i >= sel.rangeCount) {
+ return;
+ }
+ var r = sel.getRangeAt(i);
+ is(r.startContainer, expected[0], e.id + ": range.startContainer");
+ is(r.startOffset, expected[1], e.id + ": range.startOffset");
+ is(r.endContainer, expected[2], e.id + ": range.endContainer");
+ is(r.endOffset, expected[3], e.id + ": range.endOffset");
+}
+
+function checkRange(i, expected, e) {
+ var sel = window.getSelection();
+ if (i >= sel.rangeCount) {
+ return;
+ }
+ var r = sel.getRangeAt(i);
+ is(
+ r.startContainer,
+ getNode(e, expected[0]),
+ e.id + ": range[" + i + "].startContainer"
+ );
+ is(r.startOffset, expected[1], e.id + ": range[" + i + "].startOffset");
+ is(
+ r.endContainer,
+ getNode(e, expected[2]),
+ e.id + ": range[" + i + "].endContainer"
+ );
+ is(r.endOffset, expected[3], e.id + ": range[" + i + "].endOffset");
+}
+
+function checkRanges(arr, e) {
+ checkRangeCount(arr.length, e);
+ for (i = 0; i < arr.length; ++i) {
+ var expected = arr[i];
+ checkRange(i, expected, e);
+ }
+}
diff --git a/layout/base/tests/sendimagenevercomplete.sjs b/layout/base/tests/sendimagenevercomplete.sjs
new file mode 100644
index 0000000000..9c2b6f9bee
--- /dev/null
+++ b/layout/base/tests/sendimagenevercomplete.sjs
@@ -0,0 +1,29 @@
+function getFileStream(filename) {
+ // Get the location of this sjs file, and then use that to figure out where
+ // to find where our other files are.
+ var self = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ self.initWithPath(getState("__LOCATION__"));
+ var file = self.parent;
+ file.append(filename);
+
+ var fileStream = Cc[
+ "@mozilla.org/network/file-input-stream;1"
+ ].createInstance(Ci.nsIFileInputStream);
+ fileStream.init(file, 1, 0, false);
+
+ return fileStream;
+}
+
+function handleRequest(request, response) {
+ // partial.png is a truncated png
+ // by calling processAsync and not calling finish we send the truncated png
+ // in it's entirety but the webserver doesn't close to connection to indicate
+ // that all data has been delivered.
+ response.processAsync();
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "image/png", false);
+
+ var inputStream = getFileStream("partial.png");
+ response.bodyOutputStream.writeFrom(inputStream, inputStream.available());
+ inputStream.close();
+}
diff --git a/layout/base/tests/stylesheet_change_events.css b/layout/base/tests/stylesheet_change_events.css
new file mode 100644
index 0000000000..611907d3d7
--- /dev/null
+++ b/layout/base/tests/stylesheet_change_events.css
@@ -0,0 +1 @@
+* {}
diff --git a/layout/base/tests/test_accessiblecaret_magnifier.html b/layout/base/tests/test_accessiblecaret_magnifier.html
new file mode 100644
index 0000000000..2ac97cccc5
--- /dev/null
+++ b/layout/base/tests/test_accessiblecaret_magnifier.html
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1639087
+-->
+<head>
+ <title>Test for magnifier event</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1639087">Mozilla Bug 1639087</a>
+<p id="display">
+ <div id="container" style="width: 600px; height: 500px;"></div>
+</p>
+<script>
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({
+ "set": [
+ ["layout.accessiblecaret.enabled", true],
+ ["layout.accessiblecaret.magnifier.enabled", true]
+ ]
+}, () => {
+ let iframe = document.createElement("iframe");
+ iframe.src = "accessiblecaret_magnifier.html";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ document.getElementById('container').appendChild(iframe);
+});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_after_paint_pref.html b/layout/base/tests/test_after_paint_pref.html
new file mode 100644
index 0000000000..d63dd7dc0f
--- /dev/null
+++ b/layout/base/tests/test_after_paint_pref.html
@@ -0,0 +1,123 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=608030
+-->
+<head>
+ <title>Test for MozAfterPaint pref Bug 608030</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=608030">Mozilla Bug 608030</a>
+<div id="display" style="width: 10em; height: 5em; background-color: red"></div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 608030 **/
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+window.addEventListener("load", step0);
+
+is(SpecialPowers.getBoolPref("dom.send_after_paint_to_content"), true, "pref defaults to true in mochitest harness");
+
+function print_rect(rect) {
+ if (!rect) {
+ rect = { top: 0, left: 0, width: 0, right: 0 };
+ }
+ return "(top=" + rect.top + ",left=" + rect.left + ",width=" + rect.width + ",height=" + rect.height + ")";
+}
+
+function print_event(event) {
+ var res = "boundingClientRect=" + print_rect(event.boundingClientRect);
+ var rects = event.clientRects;
+ if (rects) {
+ for (var i = 0; i < rects.length; ++i) {
+ res += " clientRects[" + i + "]=" + print_rect(rects[i]);
+ }
+ }
+ return res;
+}
+
+function step0(event) {
+ // Wait until we get the MozAfterPaint following the load event
+ // before starting.
+ ok(true, "loaded");
+ window.addEventListener("MozAfterPaint", step1);
+
+ // Ensure a MozAfterPaint event is fired
+ div.style.backgroundColor = "yellow";
+}
+
+var start;
+var div = document.getElementById("display");
+
+function step1(event)
+{
+ ok(true, "step1 reached: " + print_event(event));
+ window.removeEventListener("MozAfterPaint", step1);
+
+ start = Date.now();
+
+ window.addEventListener("MozAfterPaint", step2);
+
+ div.style.backgroundColor = "blue";
+}
+
+async function step2(event)
+{
+ ok(true, "step2 reached: " + print_event(event));
+ window.removeEventListener("MozAfterPaint", step2);
+
+ var end = Date.now();
+ var timeout = 3 * Math.max(end - start, 300);
+
+ ok(true, "got MozAfterPaint (timeout for next step is " + timeout + "ms)");
+
+ // Set the pref for our second test
+
+ // When there was previously another page in our window, we seem to
+ // get duplicate events, simultaneously, so we need to register our
+ // next listener after a zero timeout.
+ await SpecialPowers.pushPrefEnv({'set': [['dom.send_after_paint_to_content', false]]});
+
+ // Wait for a double-rAF, to ensure we get a refresh driver tick (which, for
+ // this pref, is what actually makes the pref-adjustment take effect).
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ await new Promise(resolve => requestAnimationFrame(resolve));
+
+ setTimeout(step3, 0, timeout);
+}
+function step3(timeout)
+{
+ ok(true, "step3 reached");
+ window.addEventListener("MozAfterPaint", failstep);
+
+ div.style.backgroundColor = "fuchsia";
+
+ setTimeout(step4, timeout);
+}
+
+function failstep(event)
+{
+ ok(true, "failstep reached: " + print_event(event));
+ ok(false, "got MozAfterPaint when we should not have");
+}
+
+function step4()
+{
+ ok(true, "step4 reached"); // If we didn't get the failure in failstep,
+ // then we passed.
+
+ window.removeEventListener("MozAfterPaint", failstep);
+
+ // Set the pref back in its initial state.
+ SpecialPowers.pushPrefEnv({'set': [['dom.send_after_paint_to_content', true]]}, SimpleTest.finish);
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_border_radius_hit_testing.html b/layout/base/tests/test_border_radius_hit_testing.html
new file mode 100644
index 0000000000..9a78408141
--- /dev/null
+++ b/layout/base/tests/test_border_radius_hit_testing.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=595652
+-->
+<head>
+ <title>Test for Bug 595652</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=595652">Mozilla Bug 595652</a>
+<iframe src="border_radius_hit_testing_iframe.html" id="iframe" height="600" width="500" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 595652 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+
+ var doc = iframe.contentDocument;
+
+ var container = doc.body;
+ var one = doc.getElementById("one");
+ var two = doc.getElementById("two").firstChild;
+
+ //container.addEventListener("click", function(event) { alert(event.clientX + "," + event.clientY) }, false);
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y), expected_element,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(42, 6, container, "outside top left corner of #one");
+ check(11, 21, container, "outside top left corner of #one");
+ check(476, 10, container, "outside top right corner of #one");
+ check(495, 28, container, "outside top right corner of #one");
+ check(483, 197, container, "outside bottom right corner of #one");
+ check(497, 175, container, "outside bottom right corner of #one");
+ check(29, 182, container, "outside bottom left corner of #one");
+ check(73, 198, container, "outside bottom left corner of #one");
+
+ check(95, 2, one, "inside top left corner of #one (curved quadrant)");
+ check(16, 27, one, "inside top left corner of #one (curved quadrant)");
+ check(87, 37, one, "inside top left corner of #one (curved quadrant)");
+ check(465, 10, one, "inside top right corner of #one (curved quadrant)");
+ check(489, 33, one, "inside top right corner of #one (curved quadrant)");
+ check(443, 56, one, "inside top right corner of #one (curved quadrant)");
+ check(493, 167, one, "inside bottom right corner of #one (curved quadrant)");
+ check(476, 188, one, "inside bottom right corner of #one (curved quadrant)");
+ check(462, 144, one, "inside bottom right corner of #one (curved quadrant)");
+ check(74, 186, one, "inside bottom left corner of #one (curved quadrant)");
+ check(16, 153, one, "inside bottom left corner of #one (curved quadrant)");
+ check(112, 124, one, "inside bottom left corner of #one (curved quadrant)");
+
+ check(250, 1, one, "along top edge of #one");
+ check(250, 199, one, "along bottom edge of #one");
+ check(1, 100, one, "along left edge of #one");
+ check(499, 100, one, "along right edge of #one");
+ check(250, 100, one, "in center of #one");
+
+ check(2, 52, one, "inside top left corner of #one (left edge, outside ellipse)");
+ check(82, 52, one, "inside top left corner of #one (left edge, inside ellipse)");
+
+ check(42, 306, container, "outside top left corner of #two");
+ check(11, 321, container, "outside top left corner of #two");
+ check(476, 310, container, "outside top right corner of #two");
+ check(495, 328, container, "outside top right corner of #two");
+ check(483, 497, container, "outside bottom right corner of #two");
+ check(497, 475, container, "outside bottom right corner of #two");
+ check(29, 482, container, "outside bottom left corner of #two");
+ check(73, 498, container, "outside bottom left corner of #two");
+
+ check(95, 302, two, "inside top left corner of #two (curved quadrant)");
+ check(16, 327, two, "inside top left corner of #two (curved quadrant)");
+ check(87, 337, two, "inside top left corner of #two (curved quadrant)");
+ check(465, 310, two, "inside top right corner of #two (curved quadrant)");
+ check(489, 333, two, "inside top right corner of #two (curved quadrant)");
+ check(443, 356, two, "inside top right corner of #two (curved quadrant)");
+ check(493, 467, two, "inside bottom right corner of #two (curved quadrant)");
+ check(476, 488, two, "inside bottom right corner of #two (curved quadrant)");
+ check(462, 444, two, "inside bottom right corner of #two (curved quadrant)");
+ check(74, 486, two, "inside bottom left corner of #two (curved quadrant)");
+ check(16, 453, two, "inside bottom left corner of #two (curved quadrant)");
+ check(112, 424, two, "inside bottom left corner of #two (curved quadrant)");
+
+ check(250, 301, two, "along top edge of #two");
+ check(250, 499, two, "along bottom edge of #two");
+ check(1, 400, two, "along left edge of #two");
+ check(499, 400, two, "along right edge of #two");
+ check(250, 400, two, "in center of #two");
+
+ check(2, 352, two, "inside top left corner of #two (left edge, outside ellipse)");
+ check(82, 352, two, "inside top left corner of #two (left edge, inside ellipse)");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1078327.html b/layout/base/tests/test_bug1078327.html
new file mode 100644
index 0000000000..aae98a47f5
--- /dev/null
+++ b/layout/base/tests/test_bug1078327.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1078327
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1078327</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(startTest);
+ }
+ function startTest() {
+ iframe.src = "bug1078327_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1080360.html b/layout/base/tests/test_bug1080360.html
new file mode 100644
index 0000000000..bbd1af616e
--- /dev/null
+++ b/layout/base/tests/test_bug1080360.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080360
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080360</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(startTest);
+ }
+ function startTest() {
+ iframe.src = "bug1080360_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1080361.html b/layout/base/tests/test_bug1080361.html
new file mode 100644
index 0000000000..c3db21d755
--- /dev/null
+++ b/layout/base/tests/test_bug1080361.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1080361
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1080361</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(startTest);
+ }
+ function startTest() {
+ iframe.src = "bug1080361_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1093686.html b/layout/base/tests/test_bug1093686.html
new file mode 100644
index 0000000000..8305c38535
--- /dev/null
+++ b/layout/base/tests/test_bug1093686.html
@@ -0,0 +1,41 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1093686
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Testing effect of listener on body with respect to event retargeting</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ turnOnEventRetargeting(startTest);
+ }
+ function turnOnEventRetargeting(callback) {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 8],
+ ["ui.mouse.radius.topmm", 12],
+ ["ui.mouse.radius.rightmm", 8],
+ ["ui.mouse.radius.bottommm", 4]
+ ]
+ }, callback);
+ }
+ function startTest() {
+ iframe.src = "bug1093686_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1120705.html b/layout/base/tests/test_bug1120705.html
new file mode 100644
index 0000000000..8c33b6878c
--- /dev/null
+++ b/layout/base/tests/test_bug1120705.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1120705</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 id="display"></p>
+<select id="sel">
+ <option value="1">1</option>
+ <option value="2">2</option>
+ <option value="3">3</option>
+ <option value="4">4</option>
+ <option value="5">5</option>
+ <option value="6">6</option>
+ <option value="7">7</option>
+ <option value="8">8</option>
+ <option value="9">9</option>
+ <option value="10">10</option>
+ <option value="11">11</option>
+ <option value="12">12</option>
+ <option value="13">13</option>
+ <option value="14">14</option>
+ <option value="15">15</option>
+ <option value="16">16</option>
+ <option value="17">17</option>
+ <option value="18">18</option>
+ <option value="19">19</option>
+ <option value="20">20</option>
+</select>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+
+function clickDownButton() {
+ var sel = document.getElementById("sel");
+ var scrollbar_width = sel.offsetWidth - sel.clientWidth;
+ synthesizeMouse(sel,
+ sel.offsetWidth - scrollbar_width / 2,
+ sel.offsetHeight - scrollbar_width / 2,
+ { type: "mousedown" });
+
+ synthesizeMouse(sel,
+ sel.offsetWidth - scrollbar_width / 2,
+ sel.offsetHeight - scrollbar_width / 2,
+ { type: "mouseup" });
+}
+
+function waitForScrollEvent(aWindow) {
+ return new Promise(resolve => {
+ aWindow.addEventListener("scroll", () => { SimpleTest.executeSoon(resolve); }, {once: true, capture: true});
+ });
+}
+
+SimpleTest.waitForExplicitFinish();
+
+
+addLoadEvent(function() {
+ SpecialPowers.pushPrefEnv({
+ "set": [["general.smoothScroll", false],
+ ["toolkit.scrollbox.verticalScrollDistance", 3]]},
+ async function() {
+ var sel = document.getElementById("sel");
+ sel.size = 2;
+ sel.scrollTo(0, 0);
+ await waitToClearOutAnyPotentialScrolls(window);
+ is(sel.scrollTop, 0, "sanity check that we start scrolling from 0");
+ let waitForScrolling = waitForScrollEvent(window);
+ clickDownButton();
+ await waitForScrolling;
+ var restricted_top = sel.scrollTop;
+ isnot(restricted_top, 0,
+ "Expected to scroll when clicking scrollbar button");
+ sel.size = 10;
+ sel.scrollTo(0, 0);
+ await waitToClearOutAnyPotentialScrolls(window);
+ is(sel.scrollTop, 0, "sanity check that we start scrolling from 0");
+ waitForScrolling = waitForScrollEvent(window);
+ clickDownButton();
+ await waitForScrolling;
+ isnot(sel.scrollTop, 0,
+ "Expected to scroll when clicking scrollbar button");
+ isnot(sel.scrollTop, restricted_top,
+ "Scrollbar button scrolling should be limited by page size");
+ SimpleTest.finish();
+ });
+});
+</script>
+</pre>
+</body>
+
+</html>
+
diff --git a/layout/base/tests/test_bug114649.html b/layout/base/tests/test_bug114649.html
new file mode 100644
index 0000000000..765a3b069e
--- /dev/null
+++ b/layout/base/tests/test_bug114649.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=114649
+-->
+<head>
+ <title>Test for Bug 114649</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=114649">Mozilla Bug 114649</a>
+<iframe id="display" style="width: 500px; height: 500px;"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 114649 **/
+
+var gIFrame;
+var gCurrentWidth = 500;
+var gGotEventsAt = [];
+var gInterval;
+var gFinished = false;
+
+function run() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("Simulating dragging to resize a window");
+
+ gIFrame = document.getElementById("display");
+
+ var subdoc = gIFrame.contentDocument;
+ subdoc.open();
+ subdoc.write("<body onresize='window.parent.handle_resize_event()'>");
+ subdoc.close();
+
+ setTimeout(do_a_resize, 50);
+}
+
+function do_a_resize()
+{
+ // decrease the width by 10 until we hit 400, then stop
+ gCurrentWidth -= 10;
+ gIFrame.style.width = gCurrentWidth + "px";
+
+ if (gCurrentWidth > 400) {
+ setTimeout(do_a_resize, 50);
+ }
+}
+
+function handle_resize_event()
+{
+ gGotEventsAt.push(gCurrentWidth);
+
+ if (gCurrentWidth == 400) {
+ check_resize_events();
+ }
+}
+
+function check_resize_events()
+{
+ if (gFinished) {
+ // We can get here when the browser can't process the resizes and
+ // dispatch resize events as fast as we're doing the resizing. We can
+ // then end up with multiple resize events queued up after we set our
+ // final size. This return makes sure that in that case we avoid
+ // calling SimpleTest.finish() more than once.
+ return;
+ }
+ gFinished = true;
+ ok(gGotEventsAt.length >= 2, "We should get more than one event");
+ isnot(gGotEventsAt[0], 400, "The first event shouldn't be for the final size");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1153130.html b/layout/base/tests/test_bug1153130.html
new file mode 100644
index 0000000000..e4b08eb3c3
--- /dev/null
+++ b/layout/base/tests/test_bug1153130.html
@@ -0,0 +1,30 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1153130
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1153130</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(startTest);
+ }
+ function startTest() {
+ iframe.src = "bug1153130_inner.html";
+ }
+ function finishTest() {
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1162990.html b/layout/base/tests/test_bug1162990.html
new file mode 100644
index 0000000000..2f1f1377ee
--- /dev/null
+++ b/layout/base/tests/test_bug1162990.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1162990
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1162990</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var iframe;
+ var number = 0;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(finishTest);
+ }
+ function finishTest() {
+ // Try to run several tests named as bug1162990_inner_<number>.html
+ if(++number < 3)
+ iframe.src = "bug1162990_inner_" + number + ".html";
+ else
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1216483.html b/layout/base/tests/test_bug1216483.html
new file mode 100644
index 0000000000..ac05a703ac
--- /dev/null
+++ b/layout/base/tests/test_bug1216483.html
@@ -0,0 +1,204 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=1216483
+-->
+<meta charset="utf-8">
+<title>Test for Bug 1216483</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1216483">Mozilla Bug 1216483</a>
+<style>
+ .container {
+ border: 1px solid black;
+ margin-bottom: 1em;
+ }
+
+ .with-before {
+ counter-reset: cnt;
+ }
+
+ .with-before .container {
+ counter-increment: cnt;
+ }
+
+ td {
+ width: 100px;
+ }
+
+ .with-before td {
+ counter-increment: cnt;
+ }
+
+ .with-before .container::before {
+ content: counter(cnt) ")";
+ margin: 0 8px;
+ color: grey;
+ }
+
+ .with-before td::before {
+ content: counter(cnt) ")";
+ margin: 0 8px;
+ color: grey;
+ }
+
+ .block .container { display: block; }
+ .flex .container { display: flex; }
+ .grid .container { display: grid; }
+</style>
+
+<h1>Only editable content</h1>
+<h2>display: block</h2>
+<div class="editor block" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>display: flex</h2>
+<div class="editor flex" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>display: grid</h2>
+<div class="editor grid" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>Table</h2>
+<div class="editor table" contenteditable>
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ <table border="1">
+ <tr><td class="second">a<br>b<br>c</td><td>d</td></tr>
+ <tr><td>k<br>l<br>m</td><td>n</td></tr>
+ </table>
+</div>
+
+<h2>Table with element inside</h2>
+<div class="editor table" contenteditable>
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ <table border="1">
+ <tr><td><span class="second">a<br>b<br>c</span></td><td><span>d</span></td></tr>
+ <tr><td><span>k<br>l<br>m</span></td><td><span>n</span></td></tr>
+ </table>
+</div>
+
+<h2>Table in a flex container</h2>
+<div class="editor flex table" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <table border="1">
+ <tr><td><span class="second">a<br>b<br>c</span></td><td><span>d</span></td></tr>
+ <tr><td><span>k<br>l<br>m</span></td><td><span>n</span></td></tr>
+ </table>
+ </div>
+</div>
+
+<h1>Some non-editable content involved and skipped</h1>
+
+<h2>display: block</h2>
+<div class="editor block with-before" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>display: flex</h2>
+<div class="editor flex with-before" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>Flex with contenteditable="false"</h2>
+<div class="editor flex" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div contenteditable="false">X</div>
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>display: grid</h2>
+<div class="editor grid with-before" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <div class="second">a<br>b<br>c</div>
+ </div>
+</div>
+
+<h2>Table</h2>
+<div class="editor table" contenteditable>
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ <table border="1">
+ <tr><td class="second">a<br>b<br>c</td><td>d</td></tr>
+ <tr><td>k<br>l<br>m</td><td>n</td></tr>
+ </table>
+</div>
+
+<h2>Table with element inside</h2>
+<div class="editor table with-before" contenteditable>
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ <table border="1">
+ <tr><td><span class="second">a<br>b<br>c</span></td><td><span>d</span></td></tr>
+ <tr><td><span>k<br>l<br>m</span></td><td><span>n</span></td></tr>
+ </table>
+</div>
+
+<h2>Table in a flex container</h2>
+<div class="editor flex table with-before" contenteditable>
+ <div class="container">
+ <div class="first">PLACE CURSOR HERE, THEN PRESS DOWNARROW.</div>
+ </div>
+ <div class="container">
+ <table border="1">
+ <tr><td><span class="second">a<br>b<br>c</span></td><td><span>d</span></td></tr>
+ <tr><td><span>k<br>l<br>m</span></td><td><span>n</span></td></tr>
+ </table>
+ </div>
+</div>
+
+<script>
+ const editors = document.querySelectorAll('.editor')
+ editors.forEach(function (editor) {
+ add_task(function() {
+ editor.focus();
+ const first = editor.querySelector('.first');
+ const second = editor.querySelector('.second')
+
+ document.getSelection().collapse(first.childNodes[0], 5);
+
+ sendKey('DOWN');
+
+ const sel = document.getSelection();
+ is(sel.anchorNode, second, editor.className + ': Caret not at the right node in second block')
+ is(sel.anchorOffset, 1, editor.className + ': Caret not at the expected offset')
+ });
+ });
+</script>
diff --git a/layout/base/tests/test_bug1226904.html b/layout/base/tests/test_bug1226904.html
new file mode 100644
index 0000000000..333e46f7c8
--- /dev/null
+++ b/layout/base/tests/test_bug1226904.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1226904">Mozilla Bug 1226904</a>
+<iframe src="bug1226904.html" id="iframe" height="1000" width="1000" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1226904 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var iframe = document.getElementById("iframe");
+ var doc = iframe.contentDocument;
+ var container = doc.getElementById("container");
+ var separator = doc.getElementById("separator");
+ var transformed = doc.getElementById("transformed");
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y), expected_element,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(600, 200, separator, "Separator object should be at right side");
+ check(900, 200, container, "Check bounds of separator object");
+ check(200, 600, transformed, "Transformed object should be at left side");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1246622.html b/layout/base/tests/test_bug1246622.html
new file mode 100644
index 0000000000..b3534574b1
--- /dev/null
+++ b/layout/base/tests/test_bug1246622.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for Bug 1246622</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style>
+ #grandparent {
+ position: absolute;
+ height: 100px;
+ width: 100px;
+ left: 400px;
+ transform-style: preserve-3d;
+ }
+
+ #parent {
+ transform: translateX(0px);
+ }
+
+ #child {
+ width: 100px;
+ height: 100px;
+ background-color: green;
+ transform-style: preserve-3d;
+ }
+ </style>
+</head>
+<body>
+<div id="grandparent">
+ <div id="parent">
+ <div id="child"></div>
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1246622 **/
+
+is(document.elementFromPoint(450,50), document.getElementById("child"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1278021.html b/layout/base/tests/test_bug1278021.html
new file mode 100644
index 0000000000..6a30e80cce
--- /dev/null
+++ b/layout/base/tests/test_bug1278021.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<title>Test for Bug 1278021</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+<style type="text/css">
+ #parent {
+ transform-style: preserve-3d;
+ }
+
+ #firstchild {
+ width: 100%;
+ height: 226px;
+ background-color: blue;
+ }
+
+ #secondchild {
+ display: block;
+ position: absolute;
+ top: 0px;
+ left: 0px;
+ width: 100%;
+ height: 100%;
+ opacity: 0;
+ }
+</style>
+</head>
+<body>
+<div id="parent">
+ <div id="firstchild"></div>
+ <div id="secondchild"></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1278021 **/
+
+is(document.elementFromPoint(50,50), document.getElementById("secondchild"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1448730.html b/layout/base/tests/test_bug1448730.html
new file mode 100644
index 0000000000..c9d6a24417
--- /dev/null
+++ b/layout/base/tests/test_bug1448730.html
@@ -0,0 +1,30 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1448730
+-->
+<head>
+ <title>Mozilla Bug 1448730</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=1448730">Mozilla Bug 1448730</a>
+<p id="display">
+ <div id="container" style="width: 600px; height: 500px;"></div>
+</p>
+<div id="content" style="display: none;"></div>
+<pre id="test">
+</pre>
+<script>
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPrefEnv({"set": [['layout.accessiblecaret.enabled', true]]}, () => {
+ let iframe = document.createElement("iframe");
+ iframe.src = "bug1448730.html";
+ iframe.style.width = "100%";
+ iframe.style.height = "100%";
+ document.getElementById('container').appendChild(iframe);
+});
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug1515822.html b/layout/base/tests/test_bug1515822.html
new file mode 100644
index 0000000000..1a23a0dbab
--- /dev/null
+++ b/layout/base/tests/test_bug1515822.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 1515822 - restore scroll position for display: contents children</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <style>
+ #shell {
+ display: contents;
+ }
+ #outer {
+ width: 200px;
+ height: 200px;
+ overflow: scroll;
+ }
+ #inner {
+ width: 400px;
+ height: 400px;
+ background: linear-gradient(to right bottom, white, black);
+ }
+ </style>
+</head>
+<body>
+<div id="shell">
+ <div id="outer">
+ <div id="inner">
+ </div>
+ </div>
+</div>
+<script>
+const outer = document.querySelector('#outer');
+
+outer.scrollTop = 100;
+outer.scrollLeft = 100;
+document.body.offsetTop; // flush layout
+is(outer.scrollTop, 100, 'scroll position should be moved');
+is(outer.scrollLeft, 100, 'scroll position should be moved');
+
+outer.parentElement.style.display = 'none';
+document.body.offsetTop; // flush layout
+is(outer.scrollTop, 0, 'scroll position should be zero');
+is(outer.scrollLeft, 0, 'scroll position should be zero');
+
+outer.parentElement.style.display = "";
+document.body.offsetTop; // flush layout
+is(outer.scrollTop, 100, 'scroll position should be restored');
+is(outer.scrollLeft, 100, 'scroll position should be restored');
+</script>
diff --git a/layout/base/tests/test_bug1550869_video.html b/layout/base/tests/test_bug1550869_video.html
new file mode 100644
index 0000000000..8b26533597
--- /dev/null
+++ b/layout/base/tests/test_bug1550869_video.html
@@ -0,0 +1,35 @@
+<!DOCTYPE HTML>
+<html>
+ <title>Bug 1516963: Test AccessibleCaret doesn't show when clicking on an empty video container.</title>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ border: 1px solid blue;
+ width: 100px;
+ height: 100px;
+ background-color: yellow;
+ }
+ </style>
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ document.addEventListener("selectionchange", () => {
+ ok(window.getSelection().isCollapsed, "The selection should be collapsed!");
+ });
+
+ function click() {
+ ok(window.getSelection().isCollapsed, "The selection should start collapsed!");
+ let container = document.getElementById("container");
+ synthesizeMouseAtCenter(container, {});
+ setTimeout(() => {
+ ok(window.getSelection().isCollapsed, "The selection should remain collapsed!");
+ SimpleTest.finish();
+ });
+ }
+ </script>
+ <body onload="SimpleTest.waitForFocus(click);">
+ <video id="container"></video>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug1714640.html b/layout/base/tests/test_bug1714640.html
new file mode 100644
index 0000000000..3e2de0df12
--- /dev/null
+++ b/layout/base/tests/test_bug1714640.html
@@ -0,0 +1,36 @@
+<!DOCTYPE HTML>
+<meta charset="utf-8">
+<title>Bug NNN</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<style type="text/css">
+ @font-face {
+ font-family: Ahem;
+ src: url("Ahem.ttf");
+ }
+
+ pre {
+ font: 14px/1 Ahem;
+ height: 50px;
+ overflow-y: scroll;
+ }
+</style>
+<pre id=target contenteditable>ABC
+
+
+
+
+
+<br></pre>
+<script>
+ getSelection().collapse(target.childNodes[0], 9);
+
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.waitForFocus(() => {
+ const sel = SpecialPowers.wrap(getSelection());
+ sel.scrollIntoView(0, true, 100, 0);
+ is(target.scrollTop, target.scrollTopMax);
+
+ SimpleTest.finish();
+ });
+</script>
diff --git a/layout/base/tests/test_bug1756118.html b/layout/base/tests/test_bug1756118.html
new file mode 100644
index 0000000000..ed0d553c7c
--- /dev/null
+++ b/layout/base/tests/test_bug1756118.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1756118
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1756118: A removed ResizeObserver shouldn't keep the refresh driver ticking</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1756118">Mozilla Bug 1756118</a>
+<div id="display">
+ <div id="resizeMe"></div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("need to allow time to pass so that we can " +
+ "detect unwanted extra refresh driver ticks");
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+async function addAndRemoveResizeObserver(shouldResizeTarget) {
+ // Register a resize observer:
+ let ro = new ResizeObserver(function() { info("ResizeObserver callback"); });
+ ro.observe(resizeMe);
+
+ // double-rAF to flush pending paints:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+
+ ok(gUtils.refreshDriverHasPendingTick,
+ "Expecting refresh driver to be ticking, with ResizeObserver registered");
+
+ // Unregister resize observer:
+ ro.unobserve(resizeMe);
+
+ // double-rAF to flush pending paints:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+
+ // ...and importantly: make one additional change to the document.
+ // This causes a refresh driver tick, which in buggy builds causes
+ // the refresh driver to keep ticking indefinitely. (In buggy builds, this
+ // bonus tick notifies our ResizeObserverController, and it never unregisters
+ // and hence keeps the refresh driver ticking from that point on.)
+ resizeMe.style.height = "100px";
+}
+
+async function expectTicksToStop() {
+ let didStopTicking = false;
+ // Note: The maximum loop count here is an arbitrary large value, just to let
+ // us gracefully handle edge cases where multiple setTimeouts resolve before
+ // a pending refresh driver tick. Really, we just want to be sure the refresh
+ // driver *eventually* stops ticking, and we can do so gracefully by polling
+ // with some generous-but-finite number of checks here.
+ for (var i = 0; i < 100; i++) {
+ await new Promise(r => setTimeout(r, 8));
+ if(!gUtils.refreshDriverHasPendingTick) {
+ didStopTicking = true;
+ break;
+ }
+ }
+ ok(didStopTicking, "refresh driver should have eventually stopped ticking");
+}
+
+async function run() {
+ // By default, the refresh driver ticks on its own for some period of time
+ // after pageload. Turn that off so we don't have to wait it out:
+ await SpecialPowers.pushPrefEnv({'set':
+ [['layout.keep_ticking_after_load_ms', 0]]});
+
+ // Start out with a double-rAF, to flush paints from pageload:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+
+ await addAndRemoveResizeObserver(true);
+ await expectTicksToStop();
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug332655-1.html b/layout/base/tests/test_bug332655-1.html
new file mode 100644
index 0000000000..e3d593a29a
--- /dev/null
+++ b/layout/base/tests/test_bug332655-1.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332655
+-->
+<head>
+ <title>Test for Bug 332655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332655">Mozilla Bug 332655</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput"
+ style="-moz-appearance: none"> <!-- bug 1204897 workaround -->
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 332655 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ sendString("ab ");
+ sendString("\u05d0\u05d1");
+ s1 = snapshotWindow(window);
+
+ sendString(" ");
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "space after LTR + RTL shouldn't change direction: expected " +
+ str1 + " but got " + str2);
+
+ synthesizeKey("KEY_Backspace");
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "backspace should restore the status quo: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug332655-2.html b/layout/base/tests/test_bug332655-2.html
new file mode 100644
index 0000000000..aa853321ca
--- /dev/null
+++ b/layout/base/tests/test_bug332655-2.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=332655
+-->
+<head>
+ <title>Test for Bug 332655</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=332655">Mozilla Bug 332655</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput"
+ style="-moz-appearance: none"> <!-- bug 1234659 workaround -->
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 332655 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ sendString("\u05d0");
+ sendString("ab ");
+ sendString("\u05d1\u05d2");
+ s1 = snapshotWindow(window);
+
+ // 4 LEFT to get to the beginning of the line: HOME doesn't work on OS X
+ synthesizeKey("KEY_ArrowLeft", {repeat: 4});
+ synthesizeKey("KEY_Backspace");
+ sendString("\u05d0");
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "deleting and inserting RTL char at beginning of line shouldn't change: expected " +
+ str1 + " but got " + str2);
+
+ textInput.select();
+ sendString("ab ");
+ sendString("\u05d1\u05d2");
+ // 4 LEFT to get to the beginning of the line: HOME doesn't work on OS X
+ synthesizeKey("KEY_ArrowLeft", {repeat: 4});
+ sendString("\u05d0");
+
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "the order entering Bidi text shouldn't change rendering: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug369950.html b/layout/base/tests/test_bug369950.html
new file mode 100644
index 0000000000..d53f6b70a9
--- /dev/null
+++ b/layout/base/tests/test_bug369950.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=369950
+-->
+<head>
+ <title>Test for Bug 369950</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=369950">Mozilla Bug 369950</a>
+<p id="display">
+ <iframe id="i" src="bug369950-subframe.xml" width="200" height="100"></iframe>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 369950 **/
+SimpleTest.waitForExplicitFinish();
+
+addLoadEvent(function() {
+ // Can't just run our code here, because we might not have painting
+ // unsuppressed yet. Do it async.
+ SimpleTest.executeSoon(doTheTest);
+});
+
+function doTheTest() {
+ // do a layout flush
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+
+ // And do the rest of it later
+ SimpleTest.executeSoon(reallyDoTheTest);
+}
+
+function reallyDoTheTest() {
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+
+ // We want coords relative to the iframe, so subtract off rect2.left/top.
+ // 7px is a guess to get us from the bottom of the iframe into the scrollbar
+ // groove for the horizontal scrollbar on the bottom.
+ synthesizeMouse($("i").contentDocument.documentElement,
+ -rect2.left + rect.width / 2, rect.height - rect2.top - 7,
+ {}, $("i").contentWindow);
+ // Scroll is async, so give it time
+ SimpleTest.executeSoon(checkScroll);
+};
+
+function checkScroll() {
+ // do a layout flush
+ var rect = $("i").getBoundingClientRect();
+ // And do the rest of it later
+ SimpleTest.executeSoon(reallyCheckScroll);
+}
+
+function reallyCheckScroll() {
+ var rect = $("i").getBoundingClientRect();
+ var rect2 = $("i").contentDocument.documentElement.getBoundingClientRect();
+ isnot($("i").contentWindow.scrollX, 0, "Clicking scrollbar should scroll");
+
+ // Not doing things below here, since avoiding the scroll arrows
+ // cross-platform is a huge pain.
+ SimpleTest.finish();
+ return;
+
+ // 8px horizontal offset is a guess to get us into the scr
+ synthesizeMouse($("i").contentDocument.documentElement, -rect2.left + 8,
+ rect.height - rect2.top - 7, {}, $("i").contentWindow);
+ // Scroll is async, so give it time
+ SimpleTest.executeSoon(finishUp);
+}
+
+function finishUp() {
+ is($("i").contentWindow.scrollX, 0, "Clicking scrollbar should scroll back");
+ SimpleTest.finish();
+};
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug370436.html b/layout/base/tests/test_bug370436.html
new file mode 100644
index 0000000000..e3d13ca183
--- /dev/null
+++ b/layout/base/tests/test_bug370436.html
@@ -0,0 +1,91 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=370436
+-->
+<head>
+ <title>Test for Bug 370436</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>
+
+<script class="testbody" type="application/javascript">
+words = new Array()
+
+function expandStringOffsetToWord(data, offset)
+{
+ if (data == undefined) return "";
+
+ var m1 = data.substr(0, offset).match(/\w+$/) || "";
+ var m2 = data.substr(offset).match(/^\w+/) || "";
+ return m1 + m2;
+}
+
+function onContextMenu(e)
+{
+ var node = SpecialPowers.wrap(e).rangeParent;
+ var offset = e.rangeOffset;
+
+ var word = expandStringOffsetToWord(node.data, offset);
+ words.push(word);
+}
+
+function startTest()
+{
+ var ta = document.getElementById('blah');
+ ta.focus();
+ ta.selectionStart = ta.selectionEnd = ta.value.length;
+
+ // Note: This test, intentionally or by accident, relies on sending button '0'
+ // with contextMenu, which triggers some key-equiv stuff in
+ // PresShell::AdjustContextMenuKeyEvent.
+ var mouseParams = { type: 'contextmenu', button: 0 };
+
+ /* Put cursor at start and middle of "sheep" */
+ synthesizeKey("KEY_ArrowUp")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ /* Put cursor at the end of "hello" */
+ synthesizeKey("KEY_ArrowUp")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+ synthesizeKey("KEY_ArrowRight")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ /* Put cursor on "welcome" */
+ synthesizeKey("KEY_ArrowUp")
+ synthesizeMouse(ta, 0, 0, mouseParams);
+
+ is(words.pop(), "welcome", "Word 1 selected correctly");
+ is(words.pop(), "world" , "Word 2 selected correctly");
+ is(words.pop(), "hello" , "Word 3 selected correctly");
+ is(words.pop(), "hello" , "Word 4 selected correctly");
+ is(words.pop(), "sheep" , "Word 5 selected correctly");
+ is(words.pop(), "sheep" , "Word 6 selected correctly");
+ is(words.pop(), "sheep" , "Word 7 selected correctly");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish()
+SimpleTest.waitForFocus(startTest)
+</script>
+
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=370436">Mozilla Bug 370436</a></p>
+
+<textarea id="blah" rows="10" cols="80" oncontextmenu="onContextMenu(event); return false;">
+welcome
+hello world
+sheep
+</textarea>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug386575.xhtml b/layout/base/tests/test_bug386575.xhtml
new file mode 100644
index 0000000000..b606d9d57f
--- /dev/null
+++ b/layout/base/tests/test_bug386575.xhtml
@@ -0,0 +1,46 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=386575
+-->
+<head>
+ <title>Test for Bug 386575</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=386575">Mozilla Bug 386575</a>
+<p id="display">
+
+
+<!-- the image is http://searchfox.org/mozilla-central/source/layout/reftests/bugs/solidblue.png -->
+<!-- solidblue.png is a 16x16 image -->
+<table>
+ <tbody>
+ <div style="height:20px; min-height:100%">
+ <img src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%10%00%00%00%10%08%06%00%00%00%1F%F3%FFa%00%00%00%01sRGB%00%AE%CE%1C%E9%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%20cHRM%00%00z%26%00%00%80%84%00%00%FA%00%00%00%80%E8%00%00u0%00%00%EA%60%00%00%3A%98%00%00%17p%9C%BAQ%3C%00%00%00%18tEXtSoftware%00Paint.NET%20v3.01%1C%AE%24E%00%00%00'IDAT8Ocd%10%AE%FF%CF%40%11%00%19%40%09%A6H3%D8%F5%94%D8%3Ej%00%24%F6F%03q4%0C%80%E9%00%00%D6%11y%92%15%8F%DC%CE%00%00%00%00IEND%AEB%60%82" style="height: 80%" />
+ </div>
+ </tbody>
+</table>
+
+
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+
+/** Test for Bug 386575 **/
+
+
+SimpleTest.waitForExplicitFinish();
+ok(true,"This is an assertion test");
+SimpleTest.finish();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug388019.html b/layout/base/tests/test_bug388019.html
new file mode 100644
index 0000000000..17cb35657c
--- /dev/null
+++ b/layout/base/tests/test_bug388019.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=388019
+-->
+<head>
+ <title>Test for Bug 388019</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=388019">Mozilla Bug 388019</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 388019 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run()
+{
+ var oldY = window.scrollY;
+ window.location.hash="#abc";
+ var newY = window.scrollY;
+ isnot(oldY, newY, "we scroll at all");
+ ok(oldY + 4000 < newY, "we scroll at least 4000 pixels");
+ window.scrollTo(0, 0); // make the results visible
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a name="abc"></a>You should see this text if you click on the link.
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug394057.html b/layout/base/tests/test_bug394057.html
new file mode 100644
index 0000000000..7cadd40753
--- /dev/null
+++ b/layout/base/tests/test_bug394057.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=394057
+-->
+<head>
+ <title>Test for Bug 394057</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+
+ #display { background: yellow; color: black; font-family: serif; }
+
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=394057">Mozilla Bug 394057</a>
+<table id="display"><tr><td>MmMmMm...iiiIIIlll---</td></tr></table>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 394057 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var tableElement = document.getElementById("display");
+
+var CC = SpecialPowers.Cc;
+var CI = SpecialPowers.Ci;
+
+var fe =
+ CC["@mozilla.org/gfx/fontenumerator;1"].createInstance(CI.nsIFontEnumerator);
+var serifFonts = fe.EnumerateFonts("x-western", "serif");
+var monospaceFonts = fe.EnumerateFonts("x-western", "monospace");
+
+function table_width_for_font(font) {
+ tableElement.style.fontFamily = '"' + font + '"';
+ var result = tableElement.offsetWidth;
+ tableElement.style.fontFamily = "";
+ return result;
+}
+
+var serifIdx = 0;
+var monospaceIdx = 0;
+var monospaceWidth, serifWidth;
+monospaceWidth = table_width_for_font(monospaceFonts[monospaceIdx]);
+for (serifIdx in serifFonts) {
+ serifWidth = table_width_for_font(serifFonts[serifIdx]);
+ if (serifWidth != monospaceWidth)
+ break;
+}
+if (serifWidth == monospaceWidth) {
+ for (monospaceIdx in monospaceFonts) {
+ monospaceWidth = table_width_for_font(monospaceFonts[monospaceIdx]);
+ if (serifWidth != monospaceWidth)
+ break;
+ }
+}
+
+isnot(serifWidth, monospaceWidth,
+ "can't find serif and monospace fonts of different width");
+
+SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', serifFonts[serifIdx]]]}).then(step2);
+
+var serifWidthFromPref;
+function step2() {
+ serifWidthFromPref = tableElement.offsetWidth;
+ SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}).then(step3);
+}
+var monospaceWidthFromPref;
+function step3() {
+ monospaceWidthFromPref = tableElement.offsetWidth;
+
+ is(serifWidthFromPref, serifWidth,
+ "changing font pref should change width of table (serif)");
+ is(monospaceWidthFromPref, monospaceWidth,
+ "changing font pref should change width of table (monospace)");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug399284.html b/layout/base/tests/test_bug399284.html
new file mode 100644
index 0000000000..452ab7c77d
--- /dev/null
+++ b/layout/base/tests/test_bug399284.html
@@ -0,0 +1,115 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=399284
+-->
+<head>
+ <title>Test for Bug 399284</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=399284">Mozilla Bug 399284</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+/** Test for Bug 399284 **/
+const testContent = "<p id='testPara'>The quick brown fox jumps over the lazy dog";
+
+var decoders = [
+ "Big5",
+ "Big5-HKSCS",
+ "EUC-JP",
+ "EUC-KR",
+ "gb18030",
+ "IBM866",
+ "ISO-2022-JP",
+ "ISO-8859-3",
+ "ISO-8859-4",
+ "ISO-8859-5",
+ "ISO-8859-6",
+ "ISO-8859-7",
+ "ISO-8859-8",
+ "ISO-8859-8-I",
+ "ISO-8859-10",
+ "ISO-8859-13",
+ "ISO-8859-14",
+ "ISO-8859-15",
+ "ISO-8859-16",
+ "ISO-8859-2",
+ "KOI8-R",
+ "KOI8-U",
+ "Shift_JIS",
+ "windows-1250",
+ "windows-1251",
+ "windows-1252",
+ "windows-1253",
+ "windows-1254",
+ "windows-1255",
+ "windows-1256",
+ "windows-1257",
+ "windows-1258",
+ "windows-874",
+ "x-mac-cyrillic",
+ "UTF-8",
+ "UTF-16LE",
+ "UTF-16BE"
+];
+
+var decoder;
+for (var i = 0; i < decoders.length; i++) {
+ var decoder = decoders[i];
+ var data;
+
+ // encode the content for non-ASCII compatible encodings
+ if (decoder == "UTF-16BE")
+ data = encodeUTF16BE(testContent);
+ else if (decoder == "UTF-16LE")
+ data = encodeUTF16LE(testContent);
+ else
+ data = encodeURI(testContent);
+ var dataURI = "data:text/html;charset=" + decoder + "," + data;
+
+ var testFrame = document.createElement("iframe");
+ frameID = decoder;
+ testFrame.setAttribute("id", frameID);
+ var testFrameObj = document.body.appendChild(testFrame);
+ testFrameObj.setAttribute("onload", "testFontSize('" + decoder + "')");
+ testFrameObj.contentDocument.location.assign(dataURI);
+}
+
+function encodeUTF16BE(string)
+{
+ var encodedString = "";
+ for (i = 0; i < string.length; ++i) {
+ encodedString += "%00";
+ encodedString += encodeURI(string.charAt(i));
+ }
+ return encodedString;
+}
+
+function encodeUTF16LE(string)
+{
+ var encodedString = "";
+ for (i = 0; i < string.length; ++i) {
+ encodedString += encodeURI(string.charAt(i));
+ encodedString += "%00";
+ }
+ return encodedString;
+}
+
+function testFontSize(frame)
+{
+ var iframeDoc = SpecialPowers.wrap($(frame)).contentDocument;
+ var size = parseInt(iframeDoc.defaultView.
+ getComputedStyle(iframeDoc.getElementById("testPara")).
+ getPropertyValue("font-size"));
+ ok(size > 0, "font size assigned for " + frame);
+}
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug399951.html b/layout/base/tests/test_bug399951.html
new file mode 100644
index 0000000000..9be4eaae4e
--- /dev/null
+++ b/layout/base/tests/test_bug399951.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=399951
+-->
+<head>
+ <title>Test for Bug 399951</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body style="direction: rtl" onload="document.body.style.direction = 'ltr';">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=399951">Mozilla Bug 399951</a>
+<div style="white-space: pre;">
+.i
+ h
+ f
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ function test()
+{
+/** Test for Bug 399951 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(test, 500);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug404209.xhtml b/layout/base/tests/test_bug404209.xhtml
new file mode 100644
index 0000000000..8ae2a01e5a
--- /dev/null
+++ b/layout/base/tests/test_bug404209.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=404209
+-->
+<head>
+ <title>Test for Bug 404209</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; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=404209">Mozilla Bug 404209</a>
+<table id="table" dir="rtl"><div><span id="v"><span><tfoot></tfoot>abcd</span></span></div></table>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function boom1()
+{
+ document.getElementById("table").style.borderRight = "1px solid magenta";
+ setTimeout(boom2, 400);
+}
+
+function boom2()
+{
+ var v = document.getElementById("v");
+ var newTD = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ newTD.setAttribute("width", "13%");
+ v.insertBefore(newTD, v.firstChild);
+ setTimeout(lastTest, 400);
+}
+
+function lastTest()
+{
+/** Test for Bug 404209 **/
+ ok(true, "Should not crash");
+ SimpleTest.finish();
+}
+
+ SimpleTest.requestFlakyTimeout("untriaged");
+ setTimeout(boom1, 400);
+ SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug416896.html b/layout/base/tests/test_bug416896.html
new file mode 100644
index 0000000000..894b3939c0
--- /dev/null
+++ b/layout/base/tests/test_bug416896.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=416896
+-->
+<head>
+ <title>Test for Bug 416896</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <link rel="stylesheet" type="text/css" id="l"
+ href="data:text/css,a { color: green }"/>
+ <style type="text/css" id="i"> a { color: blue; } </style>
+
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=416896">Mozilla Bug 416896</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 416896 **/
+
+ const InspectorUtils = SpecialPowers.InspectorUtils;
+
+ var inlineSheet = $("i").sheet;
+ isnot(inlineSheet, null, "Should have sheet here");
+
+ var linkedSheet = $("l").sheet;
+ isnot(linkedSheet, null, "Should have sheet here");
+
+ var inspectedRules = InspectorUtils.getCSSStyleRules(document.links[0]);
+
+ var seenInline = false;
+ var seenLinked = false;
+
+ for (var i = 0; i < inspectedRules.length; ++i)
+ {
+ var rule = inspectedRules[i];
+ var sheet = rule.parentStyleSheet;
+ if (SpecialPowers.unwrap(sheet) == inlineSheet) {
+ is(sheet.href, null, "It's an inline sheet");
+ is(seenInline, false, "Only one inline rule matches");
+ seenInline = true;
+ } else {
+ isnot(sheet.href, null, "Shouldn't have null href here " + i);
+ if (SpecialPowers.unwrap(sheet) == linkedSheet) {
+ is(seenLinked, false, "Only one linked rule matches");
+ seenLinked = true;
+ }
+ }
+ }
+
+ is(seenLinked, true, "Didn't find the linked rule?");
+ is(seenInline, true, "Didn't find the inline rule?");
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug423523.html b/layout/base/tests/test_bug423523.html
new file mode 100644
index 0000000000..69ff8e84f8
--- /dev/null
+++ b/layout/base/tests/test_bug423523.html
@@ -0,0 +1,104 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=423523
+-->
+<head>
+ <title>Test for Bug 423523</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="setTimeout(runtests, 200)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=423523">Mozilla Bug 423523</a>
+<p id="display"></p>
+
+ <table>
+ <tbody><tr>
+ <td class="tdABB" id="tdTo">
+ <p id="par1">Some text...</p></td>
+ <td>
+ <div id="div1" style="border: 1px solid silver; width: 250px;" contenteditable="true">This is some editable text.</div>
+ </td></tr>
+ </tbody></table>
+
+
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 423523 **/
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+
+
+ function divIsFocused() {
+ // Check if div is directly focused.
+ var divNode = document.getElementById("div1");
+ if (window.getSelection().focusNode == divNode) {
+ return true;
+ }
+ // Check if one of the div's children has focus.
+ var node = window.getSelection().focusNode;
+ var childNodes = divNode.childNodes;
+ for (var i=0; i<childNodes.length; i++) {
+ if (childNodes[i] == node) {
+ return true;
+ }
+ }
+ // Not focused (at least not the first gen kids, and
+ // that's ok for this test).
+ return false;
+ }
+
+ function selectionOffsetIs(expectedOffset) {
+ return window.getSelection().focusOffset == expectedOffset;
+ }
+
+ function sendMouseClick() {
+ var rect=document.getElementById('div1').getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left+1, rect.top+1, 0, 1, 0);
+ utils.sendMouseEvent('mouseup', rect.left+1, rect.top+1, 0, 1, 0);
+ }
+
+ function runtests() {
+ sendMouseClick();
+ window.getSelection().collapse(document.getElementById("div1").firstChild, 0);
+ ok(divIsFocused(), "Div should be focused [0].");
+
+ ok(divIsFocused(), "Div should be focused [1].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ synthesizeKey("KEY_ArrowLeft");
+ ok(divIsFocused(), "Div should be focused [2].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ synthesizeKey("KEY_ArrowRight");
+ ok(divIsFocused(), "Div should be focused [3].");
+ ok(selectionOffsetIs(1), "Caret should be at offset 1");
+
+ synthesizeKey("KEY_ArrowLeft");
+ ok(divIsFocused(), "Div should be focused [4].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+
+ ok(divIsFocused(), "Div should be focused [5].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ sendMouseClick();
+
+ ok(divIsFocused(), "Div should be focused [6].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ synthesizeKey("KEY_ArrowLeft");
+
+ ok(divIsFocused(), "Div should be focused [7].");
+ ok(selectionOffsetIs(0), "Caret should be at offset 0");
+ SimpleTest.finish();
+ }
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug435293-interaction.html b/layout/base/tests/test_bug435293-interaction.html
new file mode 100644
index 0000000000..6d03ca4ac9
--- /dev/null
+++ b/layout/base/tests/test_bug435293-interaction.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</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>
+ #test1 {
+ background: green;
+ height: 100px;
+ width: 100px;
+ transform: skew(30deg, 60deg) scale(2, 5) rotate(45deg) translate(2%, 50px);
+ transform-origin: 100% 50%;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<div id="content">
+ <div id="test1" onclick="testFinish();">
+ test
+ </div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runtests();
+
+function runtests() {
+ function doClick() {
+ sendMouseEvent({type: 'click'}, 'test1');
+ }
+ setTimeout(doClick, 300);
+}
+
+function testFinish(){
+ ok(1, "We can still interact with the item after it is transformed");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug435293-scale.html b/layout/base/tests/test_bug435293-scale.html
new file mode 100644
index 0000000000..70571a01b7
--- /dev/null
+++ b/layout/base/tests/test_bug435293-scale.html
@@ -0,0 +1,103 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ .test {
+ background: green;
+ height: 100px;
+ width: 100px;
+ }
+ #test1 {
+ transform: scalex(0.5);
+ }
+ #test2 {
+ transform: scaley(0.5);
+ }
+ #test3 {
+ transform: scale(0.5, 0.5);
+ }
+ #test4 {
+ transform: scale(0.5, 0.5, 0.5);
+ }
+ #test5 {
+ transform: scale(80%, none);
+ }
+ #test6 {
+ transform: scale(640000, 0.0000000000000000001);
+ }
+ #test7 {
+ transform: scale(2em, 4px);
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<p id="display"></p>
+<div id="content">
+ <div id="test1" class="test">
+ test
+ </div>
+ <p id="test2" class="test">
+ test
+ </p>
+ <div id="test3" class="test">
+ test
+ </div>
+ <div id="test4" class="test">
+ test
+ </div>
+ <div id="test5" class="test">
+ test
+ </div>
+ <div id="test6" class="test">
+ test
+ </div>
+ <div id="test7" class="test">
+ test
+ </div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+runtests();
+
+function runtests() {
+ var style = window.getComputedStyle(document.getElementById("test1"));
+ is(style.getPropertyValue("transform"), "matrix(0.5, 0, 0, 1, 0, 0)",
+ "Scalex proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test2"));
+ is(style.getPropertyValue("transform"), "matrix(1, 0, 0, 0.5, 0, 0)",
+ "Scaley proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test3"));
+ is(style.getPropertyValue("transform"), "matrix(0.5, 0, 0, 0.5, 0, 0)",
+ "Scale proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test4"));
+ is(style.getPropertyValue("transform"), "none",
+ "Three dimensional scale should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test5"));
+ is(style.getPropertyValue("transform"), "none",
+ "Percent values in scale should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test6"));
+ is(style.getPropertyValue("transform"), "matrix(640000, 0, 0, 1e-19, 0, 0)",
+ "Ensure wacky values are accepted");
+
+ style = window.getComputedStyle(document.getElementById("test7"));
+ is(style.getPropertyValue("transform"), "none",
+ "No unit values allowed in scale");
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug435293-skew.html b/layout/base/tests/test_bug435293-skew.html
new file mode 100644
index 0000000000..ce099a0c51
--- /dev/null
+++ b/layout/base/tests/test_bug435293-skew.html
@@ -0,0 +1,173 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=435293
+-->
+<head>
+ <title>Test for Bug 435293</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+ <style>
+ /* Skewed boxes can get very big. The .pane wrapper prevents them
+ from obscuring the test results. */
+ .pane {
+ height: 300px;
+ width: 300px;
+ float: left;
+ overflow: auto;
+ border: 1px solid black;
+ }
+ .test {
+ background: green;
+ height: 100px;
+ width: 100px;
+ margin: 100px;
+ }
+
+ /* Radian units are not used in this test because our CSS
+ implementation stores all dimensional values in single-
+ precision floating point, which makes it impossible to
+ hit mathematically interesting angles with radians.
+ Degrees and grads do not suffer this problem. */
+ #test1 {
+ transform: skewx(30deg);
+ }
+ #test2 {
+ transform: skewy(60deg);
+ }
+ #test3 {
+ transform: skew(45deg, 45deg);
+ }
+ #test4 {
+ transform: skew(360deg, 45deg);
+ }
+ #test5 {
+ transform: skew(45deg, 150grad);
+ }
+ #test6 {
+ transform: skew(80%, 78px);
+ }
+ #test7 {
+ transform: skew(2em, 40ex);
+ }
+ #test8 {
+ transform: skew(-45deg, -465deg);
+ }
+ #test9 {
+ transform: skew(30deg, 30deg, 30deg);
+ }
+
+ /* approach the singularity from the negative side */
+ #test10 {
+ transform: skew(50grad, 90.001deg);
+ }
+ #test11 {
+ transform: skew(300grad, 90.001deg);
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435293">Mozilla Bug 435293</a>
+<p id="display"></p>
+<div id="content">
+ <div class="pane"><div id="test1" class="test">test</div></div>
+ <div class="pane"><p id="test2" class="test">test</p></div>
+ <div class="pane"><div id="test3" class="test">test</div></div>
+ <div class="pane"><div id="test4" class="test">test</div></div>
+ <div class="pane"><div id="test5" class="test">test</div></div>
+ <div class="pane"><div id="test6" class="test">test</div></div>
+ <div class="pane"><div id="test7" class="test">test</div></div>
+ <div class="pane"><div id="test8" class="test">test</div></div>
+ <div class="pane"><div id="test9" class="test">test</div></div>
+ <div class="pane"><div id="test10" class="test">test</div></div>
+ <div class="pane"><div id="test11" class="test">test</div></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+runtests();
+
+function runtests() {
+ // For test 1 we need to handle the contingency that different systems may
+ // round differently. We will parse out the values and compare them
+ // individually. The matrix should be: matrix(1, 0, 0.57735, 1, 0, 0)
+ var style = window.getComputedStyle(document.getElementById("test1"));
+ var tformStyle = style.getPropertyValue("transform");
+ var tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test1: skewx: param 0 is 1");
+ is((+tformValues[1]), 0, "Test1: skewx: param 1 is 0");
+ ok(verifyRounded(tformValues[2], 0.57735), "Test1: skewx: Rounded param 2 is in bounds");
+ is((+tformValues[3]), 1, "Test1: skewx: param 3 is 1");
+ is((+tformValues[4]), 0, "Test1: skewx: param 4 is 0");
+ is((+tformValues[5]), 0, "Test1: skewx: param 5 is 0");
+
+ // Again, handle rounding for test 2, proper matrix should be:
+ // matrix(1, 1.73205, 0, 1, 0, 0)
+ style = window.getComputedStyle(document.getElementById("test2"));
+ tformStyle = style.getPropertyValue("transform");
+ tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test2: skewy: param 0 is 1");
+ ok(verifyRounded(tformValues[1], 1.73205), "Test2: skewy: Rounded param 1 is in bounds");
+ is((+tformValues[2]), 0, "Test2: skewy: param 2 is 0");
+ is((+tformValues[3]), 1, "Test2: skewy: param 3 is 1");
+ is((+tformValues[4]), 0, "Test2: skewy: param 4 is 0");
+ is((+tformValues[5]), 0, "Test2: skewy: param 5 is 0");
+
+ style = window.getComputedStyle(document.getElementById("test3"));
+ is(style.getPropertyValue("transform"), "matrix(1, 1, 1, 1, 0, 0)",
+ "Test3: Skew proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test4"));
+ is(style.getPropertyValue("transform"), "matrix(1, 1, 0, 1, 0, 0)",
+ "Test4: Skew angle wrap: proper matrix is applied");
+
+ style = window.getComputedStyle(document.getElementById("test5"));
+ is(style.getPropertyValue("transform"), "matrix(1, -1, 1, 1, 0, 0)",
+ "Test5: Skew mixing deg and grad");
+
+ style = window.getComputedStyle(document.getElementById("test6"));
+ is(style.getPropertyValue("transform"), "none",
+ "Test6: Skew with invalid units");
+
+ style = window.getComputedStyle(document.getElementById("test7"));
+ is(style.getPropertyValue("transform"), "none",
+ "Test7: Skew with more invalid units");
+
+ // Test 8: skew with negative degrees, here again we must handle rounding.
+ // The matrix should be: matrix(1, 3.73206, -1, 1, 0, 0)
+ style = window.getComputedStyle(document.getElementById("test8"));
+ tformStyle = style.getPropertyValue("transform");
+ tformValues = tformStyle.substring(tformStyle.indexOf('(') + 1,
+ tformStyle.indexOf(')')).split(',');
+ is((+tformValues[0]), 1, "Test8: Test skew with negative degrees-param 0 is 1");
+ ok(verifyRounded(tformValues[1], 3.73206), "Test8: Rounded param 1 is in bounds");
+ is((+tformValues[2]), -1, "Test8: param 2 is -1");
+ is((+tformValues[3]), 1, "Test8: param 3 is 1");
+ is((+tformValues[4]), 0, "Test8: param 4 is 0");
+ is((+tformValues[5]), 0, "Test8: param 5 is 0");
+
+ style = window.getComputedStyle(document.getElementById("test9"));
+ is(style.getPropertyValue("transform"), "none",
+ "Test9: Skew in 3d should be ignored");
+
+ style = window.getComputedStyle(document.getElementById("test10"));
+ is(style.getPropertyValue("transform"), "matrix(1, -10000, 1, 1, 0, 0)",
+ "Test10: Skew with nearly infinite numbers");
+
+ style = window.getComputedStyle(document.getElementById("test11"));
+ is(style.getPropertyValue("transform"), "matrix(1, -10000, 10000, 1, 0, 0)",
+ "Test11: Skew with more infinite numbers");
+}
+
+// Verifies that aVal is +/- 0.00001 of aTrueVal
+// Returns true if so, false if not
+function verifyRounded(aVal, aTrueVal) {
+ return (Math.abs(aVal - aTrueVal).toFixed(5) <= 0.00001);
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug449781.html b/layout/base/tests/test_bug449781.html
new file mode 100644
index 0000000000..3326bb0751
--- /dev/null
+++ b/layout/base/tests/test_bug449781.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=449781
+-->
+<head>
+ <title>Test for Bug 449781</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>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=449781">Mozilla Bug 449781</a>
+<p id="display">Canary</p>
+<iframe src="about:blank" id="ourFrame" style="visibility: hidden">
+</iframe>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+var s1, s2, s3, s4;
+
+/** Test for Bug 449781 **/
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function() {
+ s1 = snapshotWindow(window);
+
+ $("ourFrame").style.display = "none";
+ is($("ourFrame").offsetWidth, 0, "Unexpected width after hiding");
+ $("ourFrame").style.display = "";
+ is($("ourFrame").clientWidth, 300, "Unexpected width after showing");
+
+ s2 = snapshotWindow(window);
+
+ var equal, str1, str2;
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "Show/hide should have no effect - " +
+ "got " + str1 + " but expected " + str2);
+
+ SpecialPowers.setFullZoom($("ourFrame").contentWindow, 2);
+
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "Zoom should have no effect - " +
+ "got " + str1 + " but expected " + str2);
+
+ $("display").style.display = "none";
+
+ s4 = snapshotWindow(window);
+ [equal, str1, str2] = compareSnapshots(s3, s4, true);
+ ok(!equal, "Should be able to see the canary");
+
+ SimpleTest.finish();
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug450930.xhtml b/layout/base/tests/test_bug450930.xhtml
new file mode 100644
index 0000000000..b899724462
--- /dev/null
+++ b/layout/base/tests/test_bug450930.xhtml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<html xmlns="http://www.w3.org/1999/xhtml" xmlns:svg="http://www.w3.org/2000/svg">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=450930
+-->
+<head>
+ <title>Test for Bug 450930 (MozAfterPaint)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript"><![CDATA[
+
+/** Test for Bug 450930 **/
+SimpleTest.waitForExplicitFinish();
+var subwindow = window.open("./bug450930.xhtml", "bug450930", "width=800,height=1000");
+
+function finishTests() {
+ subwindow.close();
+ SimpleTest.finish();
+}
+
+]]></script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug469170.html b/layout/base/tests/test_bug469170.html
new file mode 100644
index 0000000000..42ab8a69f6
--- /dev/null
+++ b/layout/base/tests/test_bug469170.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=469170
+-->
+<head>
+ <title>Test for Bug 469170</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest();">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=469170">Mozilla Bug 469170</a></p>
+
+<iframe id="source" width="50" height="50" srcdoc="<html></html>"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 469170 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var source = document.getElementById('source').contentWindow;
+ rect = { left: 0, top: 0,
+ width: source.innerWidth, height: source.innerHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var components = [ "red", "green", "blue", "alpha" ];
+
+ var data = context.getImageData(0, 0, canvas.width, canvas.height).data;
+ var failed = false;
+ for (var i = 0; i < data.length; i++) {
+ if (data[i] != 0) {
+ is(data[i], 0, "pixel " + Math.floor(i/4) + " " + components[i%4]);
+ failed = true;
+ }
+ }
+ if (!failed) {
+ ok(!failed, "all pixels fully transparent");
+ }
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug471126.html b/layout/base/tests/test_bug471126.html
new file mode 100644
index 0000000000..623c5bbbe1
--- /dev/null
+++ b/layout/base/tests/test_bug471126.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=471126
+-->
+<head>
+ <title>Test for Bug 471126</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=471126">Mozilla Bug 471126</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 471126 **/
+
+function test()
+{
+ var selection = window.getSelection();
+ selection.collapse(document.documentElement, 0);
+ document.documentElement.addEventListener("click", function(){ var foo = window; });
+}
+test();
+ok(true, "Shouldn't leak");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug499538-1.html b/layout/base/tests/test_bug499538-1.html
new file mode 100644
index 0000000000..0f5d7f3d5f
--- /dev/null
+++ b/layout/base/tests/test_bug499538-1.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=499538
+-->
+<head>
+ <title>Test for Bug 499538</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=499538">Mozilla Bug 499538</a>
+<p id="display"></p>
+<div id="content">
+<input type="text" id="testInput" style="-moz-appearance:none">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 499538 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ textInput.focus();
+ sendString("a ");
+ sendString("\u0639\u063A");
+ sendString(" b");
+ s1 = snapshotWindow(window);
+
+ textInput.select();
+ sendString("a b");
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_ArrowLeft");
+ sendString("\u0639\u063A");
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "Arabic text between English words not connected: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug514127.html b/layout/base/tests/test_bug514127.html
new file mode 100644
index 0000000000..35300c9eed
--- /dev/null
+++ b/layout/base/tests/test_bug514127.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=514127
+-->
+<head>
+ <title>Test for Bug 514127</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="runTest();">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=514127">Mozilla Bug 514127</a></p>
+
+<!--
+iframe source is
+<html><body style='background: rgb(0,0,255); width: 100px; height: 50100px;'></body></html>
+-->
+<iframe id="source" width="50" height="50"
+ src="data:text/html,%3Chtml%3E%3Cbody%20style%3D%27background%3A%20rgb%280%2C0%2C255%29%3B%20width%3A%20100px%3B%20height%3A%2050100px%3B%27%3E%3C%2Fbody%3E%3C%2Fhtml%3E"></iframe>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 514127 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+
+ var source = document.getElementById('source').contentWindow;
+ var canvasWidth = 50;
+ var canvasHeight = 50;
+
+ rect = { left: 25, top: 50000,
+ width: canvasWidth, height: canvasHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var data = context.getImageData(0, 0, canvasWidth, canvasHeight).data;
+ var failed = false;
+ for (var i = 0; i < data.length; i+=4) {
+ if (data[i] != 0 || data[i+1] != 0 || data[i+2] != 255 || data[i+3] != 255) {
+ failed = true;
+ break;
+ }
+ }
+ ok(!failed, "all pixels blue");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug518777.html b/layout/base/tests/test_bug518777.html
new file mode 100644
index 0000000000..25a2e58e73
--- /dev/null
+++ b/layout/base/tests/test_bug518777.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=518777
+-->
+<head>
+ <title>Test for Bug 518777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ function dotest() {
+ var canvasWidth = 50;
+ var canvasHeight = 50;
+ var source = document.getElementById("source").contentWindow;
+ rect = { left: 25, top: 25,
+ width: canvasWidth, height: canvasHeight };
+ var canvas = SpecialPowers.snapshotRect(source, rect, "transparent");
+ var context = canvas.getContext("2d");
+
+ var data = context.getImageData(0, 0, canvasWidth, canvasHeight).data;
+ var i;
+ for (i = 0; i < data.length; i += 4) {
+ if (data[i] != 0 || data[i + 1] != 0 || data[i + 2] != 255 || data[i + 3] != 255)
+ break;
+ }
+ ok(i >= data.length, "all pixels blue");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body>
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=518777">Mozilla Bug 518777</a></p>
+
+<iframe id="source" width="50" height="50"
+ srcdoc="<html><body onload='window.scrollTo(0,99999999); document.documentElement.offsetWidth; window.parent.dotest();' style='background: rgb(0,0,255); width: 100px; height: 50100px;'></body></html>"></iframe>
+
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug548545.xhtml b/layout/base/tests/test_bug548545.xhtml
new file mode 100644
index 0000000000..d532acba34
--- /dev/null
+++ b/layout/base/tests/test_bug548545.xhtml
@@ -0,0 +1,47 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=548545
+-->
+<head>
+ <title>Test for Bug 548545</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ <style type="text/css">
+ #content { margin: 1em; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=548545">Mozilla Bug 548545</a>
+<div id="content">
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test for Bug 548545 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var content = document.getElementById("content");
+
+var CC = SpecialPowers.Cc;
+var CI = SpecialPowers.Ci;
+
+var fe =
+ CC["@mozilla.org/gfx/fontenumerator;1"].createInstance(CI.nsIFontEnumerator);
+var allFonts = fe.EnumerateFonts(null, null);
+
+var idx = 0;
+var list = "";
+for (idx in allFonts) {
+ list += allFonts[idx] + "<br/>";
+}
+content.innerHTML = list;
+
+ok(true,"Loaded the font list");
+SimpleTest.finish();
+
+]]>
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug558663.html b/layout/base/tests/test_bug558663.html
new file mode 100644
index 0000000000..ea957cc960
--- /dev/null
+++ b/layout/base/tests/test_bug558663.html
@@ -0,0 +1,37 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>Bug 558663 test</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: 600px;
+ height: 400px;
+ }
+ </style>
+ </head>
+ <body>
+ <div id="container"></div>
+ </body>
+ <script>
+ if (navigator.platform.startsWith("Linux")) {
+ // For e10s issue of bug 966157
+ SimpleTest.expectAssertions(0, 2);
+ }
+ SimpleTest.waitForExplicitFinish();
+ // AccessibleCaret's pref is checked only when PresShell is initialized. To turn
+ // off the pref, we test bug 558663 in an iframe.
+ SpecialPowers.pushPrefEnv({"set": [['layout.accessiblecaret.enabled_on_touch', false]]}, function() {
+ var iframe = document.createElement("iframe");
+ iframe.src = "bug558663.html";
+ document.getElementById('container').appendChild(iframe);
+ });
+ </script>
+</html>
diff --git a/layout/base/tests/test_bug559499.html b/layout/base/tests/test_bug559499.html
new file mode 100644
index 0000000000..6b70d86a70
--- /dev/null
+++ b/layout/base/tests/test_bug559499.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html style="background:yellow">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=559499
+-->
+<head>
+ <title>Test for Bug 559499</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body style="position:relative; z-index:-1; padding-top:100px;">
+<p><a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=559499">Mozilla Bug 514127</a></p>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 559499 **/
+
+is(document.elementFromPoint(50, 50), document.body, "Able to hit body");
+document.documentElement.style.display = "table";
+is(document.elementFromPoint(50, 50), document.body, "Able to hit body (table)");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug569520.html b/layout/base/tests/test_bug569520.html
new file mode 100644
index 0000000000..50f7d2afc1
--- /dev/null
+++ b/layout/base/tests/test_bug569520.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569520
+-->
+<head>
+ <title>Test for Bug 569520</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=569520">Mozilla Bug 569520</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569520 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var start = window.performance.now();
+var firstListenerArg;
+var secondListenerArg;
+var thirdListenerTime;
+
+// callback arg is in the same timeline as performance.now()
+function thirdListener(t) {
+ thirdListenerTime = t;
+
+ ok(secondListenerArg >= firstListenerArg, // callback args from consecutive requestAnimationFrame
+ "Second listener should fire after first listener");
+
+ ok(thirdListenerTime >= secondListenerArg,
+ "Third listener should fire after second listener");
+
+ ok(firstListenerArg >= start, "First listener should fire after start");
+
+ SimpleTest.finish();
+}
+
+// callback arg is from requestAnimationFrame and comparable to performance.now()
+function secondListener(t) {
+ secondListenerArg = t;
+ requestAnimationFrame(thirdListener);
+}
+
+function firstListener(t) {
+ firstListenerArg = t;
+ requestAnimationFrame(secondListener);
+}
+
+addLoadEvent(function() {
+ setTimeout(function() {
+ requestAnimationFrame(firstListener);
+ }, 100);
+ });
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582181-1.html b/layout/base/tests/test_bug582181-1.html
new file mode 100644
index 0000000000..b8d4ee9de6
--- /dev/null
+++ b/layout/base/tests/test_bug582181-1.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582181
+-->
+<head>
+ <title>Test for Bug 582181</title>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582181">Mozilla Bug 582181</a>
+<p id="display"></p>
+<div id="content" dir="rtl">
+<textarea rows="4" style="resize: none" id="testInput">Ùارسی
+[[en:Farsi]]</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 582181 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ s1 = snapshotWindow(window);
+
+ textInput.focus();
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_Enter");
+ textInput.blur();
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "enter after text shouldn't change rendering: expected " +
+ str1 + " but got " + str2);
+
+ textInput.focus();
+ synthesizeKey("KEY_Backspace");
+ textInput.blur();
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "backspace shouldn't change rendering: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582181-2.html b/layout/base/tests/test_bug582181-2.html
new file mode 100644
index 0000000000..8a4d1c5116
--- /dev/null
+++ b/layout/base/tests/test_bug582181-2.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582181
+-->
+<head>
+ <title>Test for Bug 582181</title>
+ <meta charset="utf-8">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="test()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582181">Mozilla Bug 582181</a>
+<p id="display"></p>
+<div id="content" dir="rtl">
+<textarea rows="5" id="testInput" style="resize:none">Blah blah
+Ùلان Ùلان
+&lt;ref&gt;ooo&lt;/ref&gt;
+&lt;references /&gt;</textarea>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 582181 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function test() {
+ var textInput = $("testInput");
+ var s1, s2, s3, equal, str1, str2;
+
+ s1 = snapshotWindow(window);
+
+ textInput.focus();
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_ArrowDown");
+ synthesizeKey("KEY_Backspace");
+ textInput.blur();
+ s2 = snapshotWindow(window);
+
+ [unequal, str1, str2] = compareSnapshots(s1, s2, false);
+ ok(unequal, "backspace after text should change rendering: got " + str2);
+
+ textInput.focus();
+ sendString(">");
+ textInput.blur();
+ s3 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s3, true);
+ ok(equal, "entering '>' should restore original rendering: expected " + str1 +
+ " but got " + str2);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug582771.html b/layout/base/tests/test_bug582771.html
new file mode 100644
index 0000000000..9c230e15ba
--- /dev/null
+++ b/layout/base/tests/test_bug582771.html
@@ -0,0 +1,128 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=582771
+-->
+<head>
+ <title>Test for Bug 582771</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .test {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ -moz-user-select: none;
+ }
+ </style>
+</head>
+<body onload="setTimeout('runTest()', 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=582771">Mozilla Bug 582771</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 582771 **/
+
+SimpleTest.waitForExplicitFinish();
+var d1;
+var d2;
+var d1mousemovecount = 0;
+var d2mousemovecount = 0;
+
+function sendMouseMove(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousemove', rect.left + 5, rect.top + 5, 0, 0, 0);
+}
+
+function sendMouseDown(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function sendMouseUp(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function log(s) {
+ document.getElementById("l").textContent += s + "\n";
+}
+
+function d2Listener(e) {
+ log(e.type + ", " + e.target.id);
+ is(e.target, d2, "d2 should have got mousemove.");
+ ++d2mousemovecount;
+}
+
+function d1Listener(e) {
+ log(e.type + ", " + e.target.id);
+ d1.setCapture(true);
+}
+
+function d1Listener2(e) {
+ log(e.type + ", " + e.target.id);
+ d2.setCapture(true);
+}
+
+function d1MouseMoveListener(e) {
+ log(e.type + ", " + e.target.id);
+ ++d1mousemovecount;
+}
+
+function runTest() {
+ d1 = document.getElementById("d1");
+ d2 = document.getElementById("d2");
+ d2.addEventListener("mousemove", d2Listener, true);
+ document.body.offsetLeft;
+ sendMouseMove(d2);
+ is(d2mousemovecount, 1, "Should have got mousemove");
+
+ // This shouldn't enable capturing, since we're not in a right kind of
+ // event listener.
+ d1.setCapture(true);
+ sendMouseDown(d1);
+ sendMouseMove(d2);
+ sendMouseUp(d1);
+ is(d2mousemovecount, 2, "Should have got mousemove");
+
+ d1.addEventListener("mousedown", d1Listener, true);
+ d1.addEventListener("mousemove", d1MouseMoveListener, true);
+ sendMouseDown(d1);
+ sendMouseMove(d2);
+ is(d2mousemovecount, 2, "Shouldn't have got mousemove");
+ is(d1mousemovecount, 1, "Should have got mousemove");
+ sendMouseUp(d1);
+ d1.removeEventListener("mousedown", d1Listener, true);
+ d1.removeEventListener("mousemove", d1MouseMoveListener, true);
+
+ // Nothing should be capturing the event.
+ sendMouseMove(d2);
+ is(d2mousemovecount, 3, "Should have got mousemove");
+
+
+ d1.addEventListener("mousemove", d1Listener2, true);
+ sendMouseDown(d1);
+ sendMouseMove(d1); // This should call setCapture to d2!
+ d1.removeEventListener("mousemove", d1Listener2, true);
+ d1.addEventListener("mousemove", d1MouseMoveListener, true);
+ sendMouseMove(d1); // This should send mouse event to d2.
+ is(d1mousemovecount, 1, "Shouldn't have got mousemove");
+ is(d2mousemovecount, 4, "Should have got mousemove");
+ sendMouseUp(d1);
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+<div class="test" id="d1">&nbsp;</div><br><div class="test" id="d2">&nbsp;</div>
+<pre id="l"></pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug583889.html b/layout/base/tests/test_bug583889.html
new file mode 100644
index 0000000000..84ddae90d5
--- /dev/null
+++ b/layout/base/tests/test_bug583889.html
@@ -0,0 +1,53 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=583889
+-->
+<head>
+ <title>Test for Bug 583889</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=583889">Mozilla Bug 583889</a>
+<iframe id="inner" style="width: 10px; height: 10px;"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 583889 **/
+SimpleTest.waitForExplicitFinish();
+
+function grabEventAndGo(event) {
+ gen.next(event);
+}
+
+function* runTest() {
+ window.onload = grabEventAndGo;
+ // Wait for onLoad event.
+ yield;
+
+ var inner = $("inner");
+ inner.src = "bug583889_inner1.html";
+ window.onmessage = grabEventAndGo;
+ // Wait for message from 'inner' iframe.
+ event = yield;
+
+ while (event.data != "done") {
+ data = JSON.parse(event.data);
+ is(data.top, 300, "should remain at same top");
+ is(data.left, 300, "should remain at same left");
+
+ // Wait for message from 'inner' iframe.
+ event = yield;
+ }
+
+ // finish(), yet let the test actually end first, to be safe.
+ SimpleTest.executeSoon(SimpleTest.finish);
+}
+
+var gen = runTest();
+gen.next();
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug588174.html b/layout/base/tests/test_bug588174.html
new file mode 100644
index 0000000000..50f7d2afc1
--- /dev/null
+++ b/layout/base/tests/test_bug588174.html
@@ -0,0 +1,67 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=569520
+-->
+<head>
+ <title>Test for Bug 569520</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=569520">Mozilla Bug 569520</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 569520 **/
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var start = window.performance.now();
+var firstListenerArg;
+var secondListenerArg;
+var thirdListenerTime;
+
+// callback arg is in the same timeline as performance.now()
+function thirdListener(t) {
+ thirdListenerTime = t;
+
+ ok(secondListenerArg >= firstListenerArg, // callback args from consecutive requestAnimationFrame
+ "Second listener should fire after first listener");
+
+ ok(thirdListenerTime >= secondListenerArg,
+ "Third listener should fire after second listener");
+
+ ok(firstListenerArg >= start, "First listener should fire after start");
+
+ SimpleTest.finish();
+}
+
+// callback arg is from requestAnimationFrame and comparable to performance.now()
+function secondListener(t) {
+ secondListenerArg = t;
+ requestAnimationFrame(thirdListener);
+}
+
+function firstListener(t) {
+ firstListenerArg = t;
+ requestAnimationFrame(secondListener);
+}
+
+addLoadEvent(function() {
+ setTimeout(function() {
+ requestAnimationFrame(firstListener);
+ }, 100);
+ });
+
+
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug603550.html b/layout/base/tests/test_bug603550.html
new file mode 100644
index 0000000000..a62ebc8723
--- /dev/null
+++ b/layout/base/tests/test_bug603550.html
@@ -0,0 +1,118 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=603550
+-->
+<head>
+ <title>Test for Bug 603550</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .test {
+ width: 20px;
+ height: 20px;
+ border: 1px solid black;
+ -moz-user-select: none;
+ }
+ </style>
+</head>
+<body onload="setTimeout('runTest()', 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=603550">Mozilla Bug 603550</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 603550 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function sendMouseMoveFaraway(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousemove', rect.left + 5000, rect.top + 5000, 0, 0, 0);
+}
+
+function sendMouseDown(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mousedown', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function sendMouseUp(el) {
+ var rect = el.getBoundingClientRect();
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.sendMouseEvent('mouseup', rect.left + 5, rect.top + 5, 0, 1, 0);
+}
+
+function fireEvent(target, event) {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ utils.dispatchDOMEventViaPresShellForTesting(target, event);
+}
+
+function fireDrop(element) {
+ var ds = SpecialPowers.Cc["@mozilla.org/widget/dragservice;1"].
+ getService(SpecialPowers.Ci.nsIDragService);
+
+ ds.startDragSessionForTests(
+ SpecialPowers.Ci.nsIDragService.DRAGDROP_ACTION_MOVE |
+ SpecialPowers.Ci.nsIDragService.DRAGDROP_ACTION_COPY |
+ SpecialPowers.Ci.nsIDragService.DRAGDROP_ACTION_LINK
+ ); // Session for getting dataTransfer object.
+ try {
+ var event = document.createEvent("DragEvent");
+ event.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+ fireEvent(element, event);
+
+ event = document.createEvent("DragEvent");
+ event.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+ fireEvent(element, event);
+ } finally {
+ ds.endDragSession(false);
+ ok(!ds.getCurrentSession(), "There shouldn't be a drag session anymore!");
+ }
+}
+
+function runTest() {
+ var d1 = document.getElementById("d1");
+ var didGetMouseMove = false;
+ sendMouseDown(d1);
+ document.addEventListener("mousemove",
+ function (e) {
+ didGetMouseMove = (e.target == document);
+ },
+ true);
+ sendMouseMoveFaraway(d1);
+ ok(didGetMouseMove, "Should have got mousemove!");
+ sendMouseUp(d1);
+
+ didGetMouseMove = false;
+ document.addEventListener("mousedown",
+ function (e) {
+ e.preventDefault();
+ },
+ true);
+ sendMouseDown(d1);
+ sendMouseMoveFaraway(d1);
+ ok(didGetMouseMove, "Should have got mousemove! (2)");
+ sendMouseUp(d1);
+
+ didGetMouseMove = false;
+ sendMouseDown(d1);
+ fireDrop(d1);
+ sendMouseMoveFaraway(d1);
+ ok(!didGetMouseMove, "Shouldn't have got mousemove!");
+
+
+
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+<div class="test" id="d1">&nbsp;</div>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug607529.html b/layout/base/tests/test_bug607529.html
new file mode 100644
index 0000000000..a74aff565c
--- /dev/null
+++ b/layout/base/tests/test_bug607529.html
@@ -0,0 +1,119 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=607529
+-->
+<head>
+ <title>Test for Bug 607529</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=607529">Mozilla Bug 607529</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ /* General idea: Open a new window (needed because we don't bfcache
+ subframes) that uses requestAnimationFrame, navigate it, navigate it
+ back, and verify that the animations are still running. */
+
+ function executeTest() {
+ /** Test for Bug 607529 **/
+ var doneOneLoad = false;
+ var done = false;
+ var bc = new BroadcastChannel("bug607529");
+ var bc_1 = new BroadcastChannel("bug607529_1");
+ bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ isnot(msg, "notcached", "Should never end up not being cached");
+ if (msg == "loaded") {
+ if (!doneOneLoad) {
+ doneOneLoad = true;
+ bc.postMessage("navigateToPage");
+ } else {
+ // This is unexpected, but it can happen on Android, probably when
+ // bfcache gets purged due to memory pressure. Hence, "soft fail" there.
+ var message = "onload handler shouldn't fire on restore from bfcache";
+ if (navigator.appVersion.includes("Android")) {
+ todo(false, message);
+ } else {
+ ok(false, message);
+ }
+ // In any case, more messages aren't coming, so finish up.
+ closeWindowAndFinish();
+ }
+ }
+ else if (msg == "revived") {
+ bc.postMessage("report");
+ }
+ else if (msg == "callbackHappened") {
+ // We might get this message more than once, if the other page queues up
+ // more than one callbackHappened message before we manage to close it.
+ // Protect against calling SimpleTest.finish() more than once.
+ if (!done) {
+ closeWindowAndFinish();
+ done = true;
+ }
+ } else if (msg == "closed") {
+ bc.close();
+ bc_1.close();
+ SimpleTest.finish();
+ } else {
+ try {
+ var jsonMsg = JSON.parse(msg);
+ } catch (ex) {
+ // In case JSON.parse throws, we pause to print the string that it
+ // choked on, and then resume throwing the exception.
+ ok(false, "JSON.parse threw, when passed string '" + jsonMsg + "'");
+ throw ex;
+ }
+ if (jsonMsg.error) {
+ window.onerror(jsonMsg.msg, jsonMsg.url, jsonMsg.line);
+ }
+ }
+ }
+ bc_1.onmessage = (msgEvent) => {
+ if (msgEvent.data == "goback") {
+ bc_1.postMessage("navigateBack");
+ }
+ }
+ function closeWindowAndFinish() {
+ bc.postMessage("close");
+ }
+
+ // If Fission is disabled, the pref is no-op.
+ SpecialPowers.pushPrefEnv({set: [["fission.bfcacheInParent", true]]}, () => {
+ window.open("file_bug607529.html", "", "noopener");
+ });
+ }
+
+ if (isXOrigin) {
+ // Bug 1746646: Make mochitests work with TCP enabled (cookieBehavior = 5)
+ // Acquire storage access permission here so that the BroadcastChannel used to
+ // communicate with the opened windows works in xorigin tests. Otherwise,
+ // the iframe containing this page is isolated from first-party storage access,
+ // which isolates BroadcastChannel communication.
+ SpecialPowers.wrap(document).notifyUserGestureActivation();
+ SpecialPowers.pushPrefEnv({
+ set: [["privacy.partition.always_partition_third_party_non_cookie_storage", false]],
+ }).then(() => {
+ SpecialPowers.addPermission("storageAccessAPI", true, window.location.href).then(() => {
+ SpecialPowers.wrap(document).requestStorageAccess().then(() => {
+ executeTest();
+ });
+ });
+ });
+ } else {
+ executeTest();
+ }
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug629838.html b/layout/base/tests/test_bug629838.html
new file mode 100644
index 0000000000..94c60a9557
--- /dev/null
+++ b/layout/base/tests/test_bug629838.html
@@ -0,0 +1,87 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for MozAfterPaint</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display">
+<div width="100" height="100" id="p" style="background-color: rgb(0,0,0)"/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+var initialPaintCount, afterPaintCount;
+var color = 0;
+
+function onAfterPaint () {
+ afterPaintCount += 1;
+}
+
+function startTest() {
+ setTimeout(function () {
+ afterPaintCount = 0;
+ initialPaintCount = SpecialPowers.DOMWindowUtils.paintCount;
+ window.addEventListener("MozAfterPaint", onAfterPaint, true);
+ doBackgroundFlicker();
+ }, 500);
+}
+
+document.addEventListener("DOMContentLoaded", startTest, true);
+
+// Unfortunately we cannot reliably assert that mozPaintCount and afterPaintCount increment perfectly
+// in sync, because they can diverge in the presence of OS-triggered paints or system load.
+// Instead, wait for a minimum number of afterPaint events to at least ensure that they are being fired.
+const minimumAfterPaintsToPass = 10;
+
+function doElementFlicker() {
+ ok(true, "Element color iteration " + color +
+ ", afterpaint count: " + afterPaintCount +
+ ", mozpaint count: " + SpecialPowers.DOMWindowUtils.paintCount);
+ if (afterPaintCount >= minimumAfterPaintsToPass) {
+ ok(true, "afterPaintCount incremented enough from color changes.");
+ SimpleTest.finish();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.getElementById("p").style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doElementFlicker, 0);
+}
+
+function doBackgroundFlicker() {
+ ok(true, "Background color iteration " + color +
+ ", afterpaint count: " + afterPaintCount +
+ ", mozpaint count: " + SpecialPowers.DOMWindowUtils.paintCount);
+ if (afterPaintCount >= minimumAfterPaintsToPass) {
+ ok(true, "afterPaintCount incremented enough from background color changes.");
+ afterPaintCount = 0;
+ initialPaintCount = SpecialPowers.DOMWindowUtils.paintCount;
+ doElementFlicker();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.body.style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doBackgroundFlicker, 0);
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug644768.html b/layout/base/tests/test_bug644768.html
new file mode 100644
index 0000000000..396fccb5d0
--- /dev/null
+++ b/layout/base/tests/test_bug644768.html
@@ -0,0 +1,62 @@
+<!-- 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/. -->
+
+<!DOCTYPE html>
+<html>
+ <!--
+ https://bugzilla.mozilla.org/show_bug.cgi?id=644768
+ -->
+ <head>
+ <title>Test for Bug 644768</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+ </head>
+ <body onload="test()">
+ <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=644768">Mozilla Bug 644768</a>
+ <p id="display"></p>
+ <div id="content">
+ <!-- test text is
+ == زادروزها ==
+ * [[Û±Û³Û°Û·]]
+ -->
+ <textarea id="testInput" dir="rtl" cols="80" rows="25" style="-moz-appearance:none">
+
+== &#x0632;&#x0627;&#x062F;&#x0631;&#x0648;&#x0632;&#x0647;&#x0627; ==
+* [[&#x06F1;&#x06F3;&#x06F0;&#x06F7;]]</textarea>
+ </div>
+ <pre id="test">
+ <script class="testbody" type="text/javascript">
+
+ /** Test for Bug 644768 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function test() {
+ var textInput = $("testInput");
+ var s1, s2, equal, str1, str2;
+
+ textInput.focus();
+ s1 = snapshotWindow(window);
+
+ synthesizeKey("KEY_ArrowUp");
+ synthesizeKey("KEY_ArrowUp");
+ synthesizeKey("KEY_ArrowUp");
+ synthesizeKey("KEY_Delete");
+ synthesizeKey("KEY_Enter");
+ // Bug 1016184: Touch caret will hide due to key event.
+ s2 = snapshotWindow(window);
+
+ [equal, str1, str2] = compareSnapshots(s1, s2, true);
+ ok(equal, "newline before bidi text shouldn't change direction: expected " +
+ str1 + " but got " + str2);
+
+ SimpleTest.finish();
+ }
+
+ </script>
+ </pre>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug646757.html b/layout/base/tests/test_bug646757.html
new file mode 100644
index 0000000000..b941643c29
--- /dev/null
+++ b/layout/base/tests/test_bug646757.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=646757
+-->
+<head>
+ <title>Test for Bug 646757</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body style="margin:0" id="body">
+<div style="height:20.3px; width:400px; background:pink" id="d1"></div>
+<div style="height:20px; width:400px; background:yellow" id="d2"></div>
+<div style="height:9.7px; width:400px;" id="space1"></div>
+<div style="height:20.7px; width:400px; background:pink" id="d3"></div>
+<div style="height:20px; width:400px; background:yellow" id="d4"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=646757">Mozilla Bug 646757</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+function testPoint(x, y, id) {
+ is(document.elementFromPoint(x, y).id, id,
+ "checking element at " + x + "," + y);
+}
+
+/** Test for Bug 646757 **/
+testPoint(200, 20, "d1");
+testPoint(200, 20.2, "d1");
+testPoint(200, 20.4, "d2");
+testPoint(200, 21, "d2");
+
+testPoint(200, 70, "d3");
+testPoint(200, 70.6, "d3");
+testPoint(200, 70.8, "d4");
+testPoint(200, 71, "d4");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug66619.html b/layout/base/tests/test_bug66619.html
new file mode 100644
index 0000000000..e2855db963
--- /dev/null
+++ b/layout/base/tests/test_bug66619.html
@@ -0,0 +1,72 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=66619
+-->
+<head>
+ <title>Test for Bug 66619</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=66619">Mozilla Bug 66619</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+/** Test for Bug 66619 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function assert_fully_in_view(element) {
+ let rect = element.getBoundingClientRect();
+ ok(rect.top >= 0, `${element.id} top`);
+ ok(rect.bottom <= window.innerHeight, `${element.id} bottom`);
+}
+
+function run() {
+ is(window.scrollY, 0, "window should initially be at top");
+ let first = document.getElementById("first");
+ let second = document.getElementById("second");
+ let third = document.getElementById("third");
+
+ first.focus();
+ let firstOffset = window.scrollY;
+ isnot(firstOffset, 0, "we scrolled to first anchor");
+ ok(firstOffset + window.innerHeight > 4000,
+ "we scrolled enough to show the anchor");
+ assert_fully_in_view(first);
+
+ window.scrollTo(0, 0);
+ second.focus();
+ let secondOffset = window.scrollY;
+ assert_fully_in_view(second);
+
+ window.scrollTo(0, 0);
+ third.focus();
+ let thirdOffset = window.scrollY;
+ assert_fully_in_view(third);
+
+ ok(secondOffset > firstOffset, "we scrolled the second line of the anchor into view");
+ ok(thirdOffset > secondOffset, "we scrolled the second line of the anchor into view");
+
+ window.scrollTo(0, 0); // make the results visible
+ SimpleTest.finish();
+}
+
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug667512.html b/layout/base/tests/test_bug667512.html
new file mode 100644
index 0000000000..fa8eb78dbf
--- /dev/null
+++ b/layout/base/tests/test_bug667512.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=667512
+-->
+<head>
+ <title>Test for Bug 667512</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>
+<table contenteditable="true"><tbody><tr><td id="b"><br id="a"></td></tr></tbody></table>
+<span style="display: list-item;direction: rtl;"></span>
+<script type="application/javascript">
+
+/** Test for Bug 667512 **/
+function appendElements() {
+ window.focus();
+ window.getSelection().collapse(document.documentElement, 0);
+
+ var x=document.getElementById('a');
+ x.remove();
+
+ var x=document.getElementById('b');
+ x.remove();
+
+ synthesizeKey("KEY_ArrowLeft");
+ synthesizeKey("KEY_ArrowRight");
+
+ ok(true, "Should not crash!");
+ SimpleTest.finish();
+}
+
+addLoadEvent(appendElements);
+SimpleTest.waitForExplicitFinish();
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug677878.html b/layout/base/tests/test_bug677878.html
new file mode 100644
index 0000000000..8e702f1d56
--- /dev/null
+++ b/layout/base/tests/test_bug677878.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=677878
+-->
+<head>
+ <title>Test for Bug 677878</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>
+ #test1 {
+ background: green;
+ height: 100px;
+ width: 100px;
+ transform: scale(20, 20);
+ transform-origin: 0 0%;
+ }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=677878">Mozilla Bug 677878</a>
+<div id="content">
+ <div id="test1">
+ <div id="test2" onclick="testFinish();">
+ test
+ </div>
+ </div>
+
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+runtests();
+
+function runtests() {
+ function doClick() {
+ document.getElementById("test2").addEventListener("mousedown", testFinish, true);
+ // Don't target the center because the center could actually be outside the
+ // viewport.
+ synthesizeMouse(document.getElementById("test2"), 10, 10, { type: "mousedown" })
+ }
+ setTimeout(doClick, 300);
+}
+
+function testFinish(event){
+ ok(true, "We can still interact with the item after it is transformed");
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug687297.html b/layout/base/tests/test_bug687297.html
new file mode 100644
index 0000000000..3395bcf9cc
--- /dev/null
+++ b/layout/base/tests/test_bug687297.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=687297
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 687297</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/SpecialPowers.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=687297">Mozilla Bug 687297</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+ <script class="testbody" type="text/javascript">
+ /** Test for Bug 687297 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ var size_a=0, size_b=0, size_c=0;
+
+ window.report_size_a = function(s) {
+ size_a = s;
+ };
+
+ window.report_size_b = function(s) {
+ size_b = s;
+ };
+
+ window.report_size_c = function(s) {
+ size_c = s;
+
+ isnot(size_a, size_b, "Font sizes are changing with global language-specific minimum font size");
+ is(size_c, size_a, "Font sizes are equal, propagating only the presentation-level base minimum font size");
+
+ SimpleTest.finish();
+ };
+
+ SpecialPowers.pushPrefEnv(
+ {'set':[["font.minimum-size.ja", 120]]},
+ function() {
+ window.open("bug687297_a.html", '_blank');
+ }
+ );
+
+ </script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug696020.html b/layout/base/tests/test_bug696020.html
new file mode 100644
index 0000000000..01273c0708
--- /dev/null
+++ b/layout/base/tests/test_bug696020.html
@@ -0,0 +1,47 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=696020
+-->
+<head>
+ <title>Test for Bug 696020</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=696020">Mozilla Bug 696020</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 696020 **/
+
+
+function startTest() {
+ var testFrame = document.getElementById("tf").contentWindow;
+ testFrame.focus();
+ var didHandleKeyEvent = false;
+ testFrame.addEventListener("keypress",
+ function(e) {
+ is(e.target, testFrame.document.body,
+ "Body element should be event target for key events!");
+ didHandleKeyEvent = true;
+ });
+ synthesizeKey("A", {}, testFrame);
+ ok(didHandleKeyEvent, "Should have handled a key event!");
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(startTest)
+
+
+</script>
+</pre>
+<iframe id="tf"></iframe>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug718809.html b/layout/base/tests/test_bug718809.html
new file mode 100644
index 0000000000..2be23412c0
--- /dev/null
+++ b/layout/base/tests/test_bug718809.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=718809
+-->
+<head>
+ <title>Test for Bug 718809</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+
+</head>
+<body style=margin:0>
+<div style="background:blue;height:50px;width:100px; transform: matrix3d(1, 0, 0, 0, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 10, 1); transform-origin:0 0"></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=718809">Mozilla Bug 718809</a>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+ var rect = document.querySelector("div").getBoundingClientRect();
+
+ is(rect.top, 0, "Incorrect bounding rect");
+ is(rect.left, 0, "Incorrect bounding rect");
+ is(rect.right, 100, "Incorrect bounding rect");
+ is(rect.bottom, 50, "Incorrect bounding rect");
+</script>
+</pre>
+</body>
+</html>
+
diff --git a/layout/base/tests/test_bug725426.html b/layout/base/tests/test_bug725426.html
new file mode 100644
index 0000000000..c7d64ff582
--- /dev/null
+++ b/layout/base/tests/test_bug725426.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=725426
+-->
+<title>Test for bug 725426</title>
+<script src=/tests/SimpleTest/SimpleTest.js></script>
+<link rel=stylesheet href=/tests/SimpleTest/test.css>
+<body style=margin:0>
+<div style="transform: perspective(200px)">
+<div style="transform: translatez(-100px);
+width:100px;height:100px;background:blue">
+</div></div>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725426">
+Mozilla Bug 725426</a>
+<pre id=test>
+<script class=testbody>
+var rect = document.querySelector("div>div").getBoundingClientRect();
+is(rect.top, 0, "Incorrect bounding rect top");
+is(rect.right, 100, "Incorrect bounding rect top");
+is(rect.bottom, 100, "Incorrect bounding rect top");
+is(rect.left, 0, "Incorrect bounding rect top");
+</script>
+</pre>
diff --git a/layout/base/tests/test_bug731777.html b/layout/base/tests/test_bug731777.html
new file mode 100644
index 0000000000..66b2f964b7
--- /dev/null
+++ b/layout/base/tests/test_bug731777.html
@@ -0,0 +1,49 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 731777</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #container {
+ position: relative;
+ height: 300px;
+ width: 300px;
+ margin: 50px 100px;
+ border: 2px solid blue;
+ background-color: #044B0A;
+
+ perspective: 500px;
+ overflow:hidden;
+ }
+
+ #inner {
+ margin: 0px;
+ width: 480px;
+ border: 2px solid blue;
+ height: 220px;
+ background-color: #844BCA;
+
+ transform: rotateY(91deg) translateX(0px) translateZ(0px);
+ transition: 5s;
+ }
+
+ </style>
+</head>
+<body>
+<div id="container">
+ <div id="inner"></div>
+</div>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 731777 **/
+
+is(document.elementFromPoint(325,170), document.getElementById("inner"), "Able to hit transformed object");
+is(document.elementFromPoint(405,170), document.getElementById("inner"), "Able to hit transformed object");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug749186.html b/layout/base/tests/test_bug749186.html
new file mode 100644
index 0000000000..fed00c36f6
--- /dev/null
+++ b/layout/base/tests/test_bug749186.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=749186
+Note that this is a crashtest, but because of the special privileges
+required, it needs to be run as a mochitest. Thus, the expected
+behavior of this test is that it actually loads and doesn't crash the
+browser.
+-->
+ <head>
+ <title>Test for Bug 749186 (Crashtest)</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script>
+ function endTest() {
+ ok(true, 'test finished without crashing');
+ SimpleTest.finish();
+ }
+
+ function removeBoldStyle() {
+ document.getElementById('b').removeAttribute('style');
+ SpecialPowers.pushPrefEnv({'set': [['font.size.inflation.emPerLine', 0]]},endTest);
+ }
+
+ function startTest() {
+ SimpleTest.waitForExplicitFinish();
+ SimpleTest.requestFlakyTimeout("untriaged");
+ SpecialPowers.pushPrefEnv({'set': [['font.size.inflation.emPerLine', 8]]},removeBoldStyle);
+ }
+
+ startTest();
+ </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=749186">Bug 749186</a>
+ <iframe id="a" style="display: none;"></iframe>
+ <div id="b" style="display: inline;"></div>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug761572.html b/layout/base/tests/test_bug761572.html
new file mode 100644
index 0000000000..250c9b559e
--- /dev/null
+++ b/layout/base/tests/test_bug761572.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=761572
+-->
+<head>
+ <title>Test for Bug 761572</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=761572">Mozilla Bug 761572</a>
+<div id="content">
+ <div id="d" style="background:lime; width:50px; height:50px" onmouseup="doUp()" onclick="doClick()"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var d = document.getElementById("d");
+
+function doUp() {
+ d.style.display = "none";
+}
+function doClick() {
+ ok(true, "Check click received");
+ SimpleTest.finish();
+}
+
+function doTest() {
+ // synthesizes a mousedown/mouseup pair
+ synthesizeMouse(d, 10, 10, {});
+}
+
+SimpleTest.waitForFocus(doTest);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug770106.html b/layout/base/tests/test_bug770106.html
new file mode 100644
index 0000000000..dc969b6095
--- /dev/null
+++ b/layout/base/tests/test_bug770106.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for Bug 770106</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<span id="s">Hello</span>
+<button><div style="pointer-events:none; position:relative; width:100px; background:yellow; left:-100px;">Kitty</div></button>
+
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 770106 **/
+
+var sRect = s.getBoundingClientRect();
+is(document.elementFromPoint(sRect.left + sRect.width/2, sRect.top + sRect.height/2),
+ document.getElementById("s"), "Correct object selected");
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug842853-2.html b/layout/base/tests/test_bug842853-2.html
new file mode 100644
index 0000000000..ee30fb8e93
--- /dev/null
+++ b/layout/base/tests/test_bug842853-2.html
@@ -0,0 +1,56 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=842853
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 842853</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 842853 **/
+
+SimpleTest.waitForExplicitFinish();
+
+async function verifyAfterLoad() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ // Allow a half pixel difference because the scroll position is aligned with
+ // screen pixels instead of CSS pixels (bug 1774315).
+ isfuzzy(win.scrollY, 500, 0.5);
+ SimpleTest.finish();
+ return;
+ }
+}
+
+function runTest() {
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,500);
+ e.setAttribute("onload","verifyAfterLoad()");
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=842853">Mozilla Bug 842853</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<iframe src="file_bug842853.html"></iframe>
+<script>
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug842853.html b/layout/base/tests/test_bug842853.html
new file mode 100644
index 0000000000..c6b5f40e95
--- /dev/null
+++ b/layout/base/tests/test_bug842853.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=842853
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 842853</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 842853 **/
+
+SimpleTest.waitForExplicitFinish();
+
+async function runTest() {
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ // Allow a half pixel difference because the scroll position is aligned with
+ // screen pixels instead of CSS pixels (bug 1774315).
+ isfuzzy(win.scrollY, 500, 0.5);
+ SimpleTest.finish();
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,500);
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=842853">Mozilla Bug 842853</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+var e = document.createElement('iframe');
+var url = 'file_bug842853-frame.html';
+e.setAttribute('src',url);
+e.setAttribute('onload','runTest()');
+document.body.appendChild(e);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug849219.html b/layout/base/tests/test_bug849219.html
new file mode 100644
index 0000000000..d116afeb78
--- /dev/null
+++ b/layout/base/tests/test_bug849219.html
@@ -0,0 +1,50 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=849219
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 849219</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 849219 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ var win = e.contentWindow;
+ if (win.location.hash != '') {
+ is(win.scrollY,0);
+ SimpleTest.finish();
+ return;
+ }
+ win.location.hash='#anchor'
+ win.scrollTo(0,0);
+ win.location.reload()
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=849219">Mozilla Bug 849219</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<script>
+
+var e = document.createElement('iframe');
+var url = 'file_bug842853-frame.html';
+e.setAttribute('src',url);
+e.setAttribute('onload','runTest()');
+document.body.appendChild(e);
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug851445.html b/layout/base/tests/test_bug851445.html
new file mode 100644
index 0000000000..a89ccce51c
--- /dev/null
+++ b/layout/base/tests/test_bug851445.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=851445
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 851445</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=851445">Mozilla Bug 851445</a>
+<p id="display"></p>
+<iframe id="f" style="width:400px; height:400px;"></iframe>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function handleLoad() {
+ f.contentWindow.scrollTo(0,100);
+ function handleLoad2() {
+ // Verify that the scroll position was retained
+ is(f.contentWindow.scrollY, 100);
+ SimpleTest.finish();
+ }
+ f.onload = handleLoad2;
+ f.contentWindow.location.reload();
+}
+
+f.src = "bug851445_helper.html?" + Math.random();
+f.onload = handleLoad;
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug851485.html b/layout/base/tests/test_bug851485.html
new file mode 100644
index 0000000000..a5f3488189
--- /dev/null
+++ b/layout/base/tests/test_bug851485.html
@@ -0,0 +1,86 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=851485
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 851485</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 type="application/javascript">
+
+ /** Test for Bug 851485 **/
+
+SimpleTest.waitForExplicitFinish();
+
+var testRunning = false;
+async function runTest() {
+ if (testRunning) {
+ return;
+ }
+ testRunning = true;
+
+ var e = document.getElementsByTagName('iframe')[0];
+ var win = e.contentWindow;
+
+ var p = new Promise((r) => { win.addEventListener("hashchange", r, { once: true }); });
+ win.location.hash = '#anchor';
+ await p;
+
+ win.scrollTo(0, 500);
+
+ p = new Promise((r) => { e.addEventListener("load", r, { once: true }); });
+ win.location.reload();
+ await p;
+
+ is(win.location.hash, "#anchor", "We set the location's hash to 'anchor'");
+ // Allow a half pixel difference because the scroll position is aligned with
+ // screen pixels instead of CSS pixels (bug 1774315).
+ isfuzzy(win.scrollY, 500, 0.5, "Reloading keeps scroll position");
+
+ var link = win.document.getElementsByTagName('a')[0];
+ var anchor = win.document.getElementsByTagName('a')[1];
+ p = new Promise((r) => {
+ var observer = new IntersectionObserver((entries) => {
+ info("IntersectionObserver: entering callback")
+ if (entries.some((entry) => entry.isIntersecting)) {
+ info("IntersectionObserver: some entry isIntersecting; disconnecting")
+ observer.disconnect();
+ r();
+ }
+ });
+ observer.observe(anchor);
+ });
+ win.document.body.offsetHeight;
+ synthesizeMouseAtCenter(link, {type: "mousedown"}, win);
+ synthesizeMouseAtCenter(link, {type: "mouseup"}, win);
+ info("Sending click")
+ sendMouseEvent({type: "click"}, link, win);
+ await p;
+ let actualScroll = win.scrollY;
+ info("Promise resolved (for IntersectionObserver). win.scrollY is " +
+ actualScroll);
+
+ ok(actualScroll > 3000, "Scrolling after load works.");
+
+ SimpleTest.finish();
+}
+
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=851485">Mozilla Bug 851485</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<iframe src="file_bug842853.html"></iframe>
+<script>
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug858459.html b/layout/base/tests/test_bug858459.html
new file mode 100644
index 0000000000..c12ea94819
--- /dev/null
+++ b/layout/base/tests/test_bug858459.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=858459
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 858459</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 type="application/javascript">
+
+ /** Test for Bug 858459 **/
+
+var result = "";
+var timeout = null;
+var clicks = 0;
+const EXPECTED_RESULT = "change select";
+
+function logEvent(ev,msg) {
+ result += ev.type + ' ' + msg;
+ ++clicks;
+ if (result.length > EXPECTED_RESULT.length)
+ finishTest();
+}
+
+document.onclick = function(event) { logEvent(event,"document"); }
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("untriaged");
+
+function finishTest() {
+ if (!timeout) return;
+ clearTimeout(timeout);
+ timeout = null;
+ is(result,EXPECTED_RESULT,"");
+ SimpleTest.finish();
+}
+
+function runTest() {
+ // Need a timeout to check that an event has _not_ occurred.
+ timeout = setTimeout(finishTest, 5000);
+ synthesizeMouseAtCenter(document.getElementById('test858459'), { });
+}
+
+ </script>
+</head>
+<body onload="SimpleTest.waitForFocus(runTest)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=858459">Mozilla Bug 858459</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test"><div><select id="test858459" size=4 onclick="logEvent(event,'select');" onchange="logEvent(event,'select');var div = document.querySelector('#test div'); div.innerHTML='<p>'+div.innerHTML; document.body.offsetHeight;"><option>1111111111111111<option>2<option>3</select></div>
+
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-1.html b/layout/base/tests/test_bug93077-1.html
new file mode 100644
index 0000000000..780f9c24a3
--- /dev/null
+++ b/layout/base/tests/test_bug93077-1.html
@@ -0,0 +1,31 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-2.html b/layout/base/tests/test_bug93077-2.html
new file mode 100644
index 0000000000..7985d63ad1
--- /dev/null
+++ b/layout/base/tests/test_bug93077-2.html
@@ -0,0 +1,31 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-3.html b/layout/base/tests/test_bug93077-3.html
new file mode 100644
index 0000000000..357a18cb5e
--- /dev/null
+++ b/layout/base/tests/test_bug93077-3.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p id=top>Top</p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#top"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-4.html b/layout/base/tests/test_bug93077-4.html
new file mode 100644
index 0000000000..c0a16596b5
--- /dev/null
+++ b/layout/base/tests/test_bug93077-4.html
@@ -0,0 +1,34 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p><a name=top>Top</a></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#TOP", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#top"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-5.html b/layout/base/tests/test_bug93077-5.html
new file mode 100644
index 0000000000..b2701a392c
--- /dev/null
+++ b/layout/base/tests/test_bug93077-5.html
@@ -0,0 +1,34 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p id=TOP>Top</p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#TOP"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug93077-6.html b/layout/base/tests/test_bug93077-6.html
new file mode 100644
index 0000000000..906ff13e2f
--- /dev/null
+++ b/layout/base/tests/test_bug93077-6.html
@@ -0,0 +1,34 @@
+<!-- Testing quirks mode. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=93077
+-->
+<head>
+ <title>Test for Bug 93077</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ #filler { height: 200cm; background: papayawhip; }
+ </style>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=93077">Mozilla Bug 93077</a>
+<p id="display"></p>
+<div id=filler>...</div>
+<p id=below></p>
+<p><a name=TOP>Top</a></p>
+<pre id="test">
+<script type="application/javascript">
+/** Test for Bug 93077 **/
+["#top", "#Top"].forEach(function(fragid) {
+ document.getElementById("below").scrollIntoView()
+ isnot(window.scrollY, 0)
+ location.hash = fragid
+ is(window.scrollY, 0)
+})
+location.hash = "#TOP"
+isnot(window.scrollY, 0)
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_bug970964.html b/layout/base/tests/test_bug970964.html
new file mode 100644
index 0000000000..9f1e94c841
--- /dev/null
+++ b/layout/base/tests/test_bug970964.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=970964
+-->
+<head>
+ <title>Test for Bug 970964</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ function testWithoutImplicitPointerCapture() {
+ var iframe = document.getElementById("testFrame");
+ iframe.src = "bug970964_inner.html";
+ }
+
+ function testWithImplicitPointerCapture() {
+ var iframe = document.getElementById("testFrame");
+ iframe.src = "bug970964_inner2.html";
+ }
+
+ function runTest() {
+ SimpleTest.waitForExplicitFinish();
+ window.addEventListener("message", (event) => {
+ if (event.data == "finishTest") {
+ SimpleTest.finish();
+ } else if (event.data == "run next") {
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.implicit_capture", true]
+ ]
+ }, testWithImplicitPointerCapture);
+ }
+ });
+
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.w3c_pointer_events.implicit_capture", false]
+ ]
+ }, testWithoutImplicitPointerCapture);
+ }
+ </script>
+</head>
+<body onload="runTest();">
+ <iframe id="testFrame" height="500" width="500"></iframe>
+</body>
+
diff --git a/layout/base/tests/test_bug977003.html b/layout/base/tests/test_bug977003.html
new file mode 100644
index 0000000000..426ac52566
--- /dev/null
+++ b/layout/base/tests/test_bug977003.html
@@ -0,0 +1,32 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=977003
+-->
+ <head>
+ <meta charset="utf-8">
+ <title>Test for Bug 977003</title>
+ <meta name="author" content="Maksim Lebedev" />
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="text/javascript">
+ var number = 0;
+ var iframe = undefined;
+ function prepareTest() {
+ SimpleTest.waitForExplicitFinish();
+ iframe = document.getElementById("testFrame");
+ SimpleTest.executeSoon(finishTest);
+ }
+ function finishTest() {
+ // Try to run several tests named as bug977003_inner_<number>.html
+ if(++number < 7)
+ iframe.src = "bug977003_inner_" + number + ".html";
+ else
+ SimpleTest.finish();
+ }
+ </script>
+ </head>
+ <body onload="prepareTest()">
+ <iframe id="testFrame" height="700" width="700"></iframe>
+ </body>
+</html>
diff --git a/layout/base/tests/test_bug990340.html b/layout/base/tests/test_bug990340.html
new file mode 100644
index 0000000000..dea6bb346c
--- /dev/null
+++ b/layout/base/tests/test_bug990340.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=990340
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 990340</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 990340 **/
+
+ function testbug990340() {
+ ok(document.querySelector('#bug990340 span').clientHeight < 100,
+ "'height' is in transition")
+ SimpleTest.finish();
+ }
+
+ SimpleTest.waitForExplicitFinish();
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=990340">Mozilla Bug 990340</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<style type="text/css">
+
+#bug990340::before {
+ content: ":before";
+}
+
+#bug990340 span {
+ display: inline-block;
+ border:1px solid blue;
+ height: 20px;
+ transition: height 30s;
+}
+
+#bug990340.s span {
+ height: 100px;
+}
+</style>
+<div id="bug990340" style="overflow:scroll">
+ <span>Transition height</span>
+</div>
+</pre>
+
+<script>
+document.body.offsetHeight;
+document.querySelector('#bug990340').className='s';
+testbug990340()
+</script>
+
+</body>
+</html>
diff --git a/layout/base/tests/test_bug993936.html b/layout/base/tests/test_bug993936.html
new file mode 100644
index 0000000000..5325b66a22
--- /dev/null
+++ b/layout/base/tests/test_bug993936.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=993936
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 993936</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 993936 **/
+
+var currentId = 0;
+var evictedTouchesCount = 0;
+
+function testtouch(aOptions) {
+ if (!aOptions)
+ aOptions = {};
+ this.identifier = aOptions.identifier || 0;
+ this.target = aOptions.target || 0;
+ this.page = aOptions.page || {x: 0, y: 0};
+ this.radius = aOptions.radius || {x: 0, y: 0};
+ this.rotationAngle = aOptions.rotationAngle || 0;
+ this.force = aOptions.force || 1;
+}
+
+function touchEvent(aOptions) {
+ if (!aOptions) {
+ aOptions = {};
+ }
+ this.ctrlKey = aOptions.ctrlKey || false;
+ this.altKey = aOptions.altKey || false;
+ this.shiftKey = aOptions.shiftKey || false;
+ this.metaKey = aOptions.metaKey || false;
+ this.touches = aOptions.touches || [];
+ this.targetTouches = aOptions.targetTouches || [];
+ this.changedTouches = aOptions.changedTouches || [];
+}
+
+function sendTouchEvent(windowUtils, aType, aEvent, aModifiers) {
+ var ids = [], xs=[], ys=[], rxs = [], rys = [],
+ rotations = [], forces = [], tiltXs = [], tiltYs = [], twists = [];
+
+ for (var touchType of ["touches", "changedTouches", "targetTouches"]) {
+ for (var i = 0; i < aEvent[touchType].length; i++) {
+ if (!ids.includes(aEvent[touchType][i].identifier)) {
+ ids.push(aEvent[touchType][i].identifier);
+ xs.push(aEvent[touchType][i].page.x);
+ ys.push(aEvent[touchType][i].page.y);
+ rxs.push(aEvent[touchType][i].radius.x);
+ rys.push(aEvent[touchType][i].radius.y);
+ rotations.push(aEvent[touchType][i].rotationAngle);
+ forces.push(aEvent[touchType][i].force);
+ tiltXs.push(0);
+ tiltYs.push(0);
+ twists.push(0);
+ }
+ }
+ }
+ return windowUtils.sendTouchEvent(aType,
+ ids, xs, ys, rxs, rys,
+ rotations, forces, tiltXs, tiltYs, twists,
+ aModifiers, 0);
+}
+
+function getSingleTouchEventForTarget(target, cwu) {
+ currentId++;
+ var bcr = target.getBoundingClientRect();
+ var touch = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: currentId,
+ });
+ var event = new touchEvent({
+ touches: [touch],
+ targetTouches: [touch],
+ changedTouches: [touch]
+ });
+ return event;
+}
+
+function getMultiTouchEventForTarget(target, cwu) {
+ currentId++;
+ var bcr = target.getBoundingClientRect();
+ var touch1 = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width/2),
+ y: Math.round(bcr.top + bcr.height/2)},
+ target: target,
+ identifier: currentId,
+ });
+ currentId++;
+ var touch2 = new testtouch({
+ page: {x: Math.round(bcr.left + bcr.width),
+ y: Math.round(bcr.top + bcr.height)},
+ target: target,
+ identifier: currentId,
+ });
+ var event = new touchEvent({
+ touches: [touch1, touch2],
+ targetTouches: [touch1, touch2],
+ changedTouches: [touch1, touch2]
+ });
+ return event;
+}
+
+function runTests() {
+ var cwu = SpecialPowers.getDOMWindowUtils(window);
+
+ var event1 = getMultiTouchEventForTarget(d0, cwu);
+ sendTouchEvent(cwu, "touchstart", event1, 0);
+ sendTouchEvent(cwu, "touchmove", event1, 0);
+ is(evictedTouchesCount, 0, "Still no evicted touches");
+
+ var event2 = getSingleTouchEventForTarget(d0, cwu);
+ sendTouchEvent(cwu, "touchstart", event2, 0);
+
+ // By now we should get touchend event
+ ok(evictedTouchesCount > 0, "Got evicted touch");
+
+ finishTest();
+}
+
+function finishTest() {
+ // Let window.onerror have a chance to fire
+ setTimeout(function() {
+ SimpleTest.finish();
+ }, 0);
+}
+
+SimpleTest.waitForExplicitFinish();
+
+ </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=993936">Mozilla Bug 993936</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+<div id="d0">
+ Test div
+</div>
+
+<script>
+var d0 = document.getElementById("d0");
+
+d0.addEventListener("touchend", function(ev) {
+ evictedTouchesCount++;
+});
+
+window.onload = function () {
+ setTimeout(function() {
+ runTests();
+ }, 0);
+}
+
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_caret_browsing_around_form_controls.html b/layout/base/tests/test_caret_browsing_around_form_controls.html
new file mode 100644
index 0000000000..8b4fb480cd
--- /dev/null
+++ b/layout/base/tests/test_caret_browsing_around_form_controls.html
@@ -0,0 +1,379 @@
+<!doctype html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<script>
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(async () => {
+ await SpecialPowers.pushPrefEnv({
+ set:[
+ ["accessibility.browsewithcaret", true],
+ ],
+ });
+ (function test_move_caret_from_before_input_text() {
+ info("Starting test_move_caret_from_before_input_text...");
+ document.body.innerHTML = '<div>abc<input value="def">ghi</div>';
+ const input = document.querySelector("input");
+ const textBefore = input.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_input_text: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ input.nextSibling,
+ "test_move_caret_from_before_input_text: caret should be moved to text node after the <input>"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_input_text: caret should be moved to start of the text node"
+ );
+ })();
+ (function test_move_caret_from_after_input_text() {
+ info("Starting test_move_caret_from_after_input_text...");
+ document.body.innerHTML = '<div>abc<input value="def">ghi</div>';
+ const input = document.querySelector("input");
+ getSelection().collapse(input.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_input_text: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ input.previousSibling,
+ "test_move_caret_from_after_input_text: caret should be moved to text node before the <input>"
+ );
+ is(
+ getSelection().focusOffset,
+ input.previousSibling.length,
+ "test_move_caret_from_after_input_text: caret should be moved to end of the text node"
+ );
+ })();
+ (function test_move_caret_from_before_double_input_text() {
+ info("Starting test_move_caret_from_before_double_input_text...");
+ document.body.innerHTML = '<div>abc<input value="def"><input value="ghi">jkl</div>';
+ const firstInput = document.querySelector("input");
+ const secondInput = firstInput.nextSibling;
+ const textBefore = firstInput.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_double_input_text: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ secondInput.nextSibling,
+ "test_move_caret_from_before_double_input_text: caret should be moved to text node after the second <input> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_double_input_text: caret should be moved to text node after the second <input> (offset)"
+ );
+ })();
+ (function test_move_caret_from_after_double_input_text() {
+ info("Starting test_move_caret_from_after_double_input_text...");
+ document.body.innerHTML = '<div>abc<input value="def"><input value="ghi">jkl</div>';
+ const firstInput = document.querySelector("input");
+ const secondInput = firstInput.nextSibling;
+ getSelection().collapse(secondInput.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_double_input_text: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ firstInput.previousSibling,
+ "test_move_caret_from_after_double_input_text: caret should be moved to text node before the first <input> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ firstInput.previousSibling.length,
+ "test_move_caret_from_after_double_input_text: caret should be moved to text node before the first <input> (offset)"
+ );
+ })();
+
+ (function test_move_caret_from_before_input_button() {
+ info("Starting test_move_caret_from_before_input_button...");
+ document.body.innerHTML = '<div>abc<input type="button" value="def">ghi</div>';
+ const input = document.querySelector("input");
+ const textBefore = input.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_input_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ input.nextSibling,
+ "test_move_caret_from_before_input_button: caret should be moved to text node after the <input>"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_input_button: caret should be moved to start of the text node"
+ );
+ })();
+ (function test_move_caret_from_after_input_button() {
+ info("Starting test_move_caret_from_after_input_button...");
+ document.body.innerHTML = '<div>abc<input type="button" value="def">ghi</div>';
+ const input = document.querySelector("input");
+ getSelection().collapse(input.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_input_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ input.previousSibling,
+ "test_move_caret_from_after_input_button: caret should be moved to text node before the <input>"
+ );
+ is(
+ getSelection().focusOffset,
+ input.previousSibling.length,
+ "test_move_caret_from_after_input_button: caret should be moved to end of the text node"
+ );
+ })();
+ (function test_move_caret_from_before_double_input_button() {
+ info("Starting test_move_caret_from_before_double_input_button...");
+ document.body.innerHTML = '<div>abc<input type="button" value="def"><input type="button" value="ghi">jkl</div>';
+ const firstInput = document.querySelector("input");
+ const secondInput = firstInput.nextSibling;
+ const textBefore = firstInput.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_double_input_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ secondInput.nextSibling,
+ "test_move_caret_from_before_double_input_button: caret should be moved to text node after the second <input> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_double_input_button: caret should be moved to text node after the second <input> (offset)"
+ );
+ })();
+ (function test_move_caret_from_after_double_input_button() {
+ info("Starting test_move_caret_from_after_double_input_button...");
+ document.body.innerHTML = '<div>abc<input type="button" value="def"><input type="button" value="ghi">jkl</div>';
+ const firstInput = document.querySelector("input");
+ const secondInput = firstInput.nextSibling;
+ getSelection().collapse(secondInput.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_double_input_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ firstInput.previousSibling,
+ "test_move_caret_from_after_double_input_button: caret should be moved to text node before the first <input> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ firstInput.previousSibling.length,
+ "test_move_caret_from_after_double_input_button: caret should be moved to text node before the first <input> (offset)"
+ );
+ })();
+
+ (function test_move_caret_from_before_button() {
+ info("Starting test_move_caret_from_before_button...");
+ document.body.innerHTML = '<div>abc<button>def</button>ghi</div>';
+ const button = document.querySelector("button");
+ const textBefore = button.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ button.nextSibling,
+ "test_move_caret_from_before_button: caret should be moved to text node after the <button>"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_button: caret should be moved to start of the text node"
+ );
+ })();
+ (function test_move_caret_from_after_button() {
+ info("Starting test_move_caret_from_after_button...");
+ document.body.innerHTML = '<div>abc<button>def</button>ghi</div>';
+ const button = document.querySelector("button");
+ getSelection().collapse(button.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ button.previousSibling,
+ "test_move_caret_from_after_button: caret should be moved to text node before the <button>"
+ );
+ is(
+ getSelection().focusOffset,
+ button.previousSibling.length,
+ "test_move_caret_from_after_button: caret should be moved to end of the text node"
+ );
+ })();
+ (function test_move_caret_from_before_double_button() {
+ info("Starting test_move_caret_from_before_double_button...");
+ document.body.innerHTML = '<div>abc<button>def</button><button>ghi</button>jkl</div>';
+ const firstButton = document.querySelector("button");
+ const secondButton = firstButton.nextSibling;
+ const textBefore = firstButton.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_double_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ secondButton.nextSibling,
+ "test_move_caret_from_before_double_button: caret should be moved to text node after the second <button> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_double_button: caret should be moved to text node after the second <button> (offset)"
+ );
+ })();
+ (function test_move_caret_from_after_double_button() {
+ info("Starting test_move_caret_from_after_double_button...");
+ document.body.innerHTML = '<div>abc<button>def</button><button>ghi</button>jkl</div>';
+ const firstButton = document.querySelector("button");
+ const secondButton = firstButton.nextSibling;
+ getSelection().collapse(secondButton.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_double_button: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ firstButton.previousSibling,
+ "test_move_caret_from_after_double_button: caret should be moved to text node before the first <button> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ firstButton.previousSibling.length,
+ "test_move_caret_from_after_double_button: caret should be moved to text node before the first <button> (offset)"
+ );
+ })();
+
+ (function test_move_caret_from_before_textarea() {
+ info("Starting test_move_caret_from_before_textarea...");
+ document.body.innerHTML = '<div>abc<textarea>def</textarea>ghi</div>';
+ const textarea = document.querySelector("textarea");
+ const textBefore = textarea.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_textarea: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ textarea.nextSibling,
+ "test_move_caret_from_before_textarea: caret should be moved to text node after the <textarea>"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_textarea: caret should be moved to start of the text node"
+ );
+ })();
+ (function test_move_caret_from_after_textarea() {
+ info("Starting test_move_caret_from_after_textarea...");
+ document.body.innerHTML = '<div>abc<textarea>def</textarea>ghi</div>';
+ const textarea = document.querySelector("textarea");
+ getSelection().collapse(textarea.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_textarea: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ textarea.previousSibling,
+ "test_move_caret_from_after_textarea: caret should be moved to text node before the <textarea>"
+ );
+ is(
+ getSelection().focusOffset,
+ textarea.previousSibling.length,
+ "test_move_caret_from_after_textarea: caret should be moved to end of the text node"
+ );
+ })();
+ (function test_move_caret_from_before_double_textarea() {
+ info("Starting test_move_caret_from_before_double_textarea...");
+ document.body.innerHTML = '<div>abc<textarea>def</textarea><textarea>ghi</textarea>jkl</div>';
+ const firstTextarea = document.querySelector("textarea");
+ const secondTextarea = firstTextarea.nextSibling;
+ const textBefore = firstTextarea.previousSibling;
+ getSelection().collapse(textBefore, textBefore.length);
+ synthesizeKey("KEY_ArrowRight");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_before_double_textarea: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ secondTextarea.nextSibling,
+ "test_move_caret_from_before_double_textarea: caret should be moved to text node after the second <textarea> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ 0,
+ "test_move_caret_from_before_double_textarea: caret should be moved to text node after the second <textarea> (offset)"
+ );
+ })();
+ (function test_move_caret_from_after_double_textarea() {
+ info("Starting test_move_caret_from_after_double_textarea...");
+ document.body.innerHTML = '<div>abc<textarea>def</textarea><textarea>ghi</textarea>jkl</div>';
+ const firstTextarea = document.querySelector("textarea");
+ const secondTextarea = firstTextarea.nextSibling;
+ getSelection().collapse(secondTextarea.nextSibling, 0);
+ synthesizeKey("KEY_ArrowLeft");
+ ok(
+ getSelection().isCollapsed,
+ "test_move_caret_from_after_double_textarea: selection should be collapsed after moving caret"
+ );
+ is(
+ getSelection().focusNode,
+ firstTextarea.previousSibling,
+ "test_move_caret_from_after_double_textarea: caret should be moved to text node before the first <textarea> (container)"
+ );
+ is(
+ getSelection().focusOffset,
+ firstTextarea.previousSibling.length,
+ "test_move_caret_from_after_double_textarea: caret should be moved to text node before the first <textarea> (offset)"
+ );
+ })();
+
+ SimpleTest.finish();
+});
+</script>
+</head>
+<body></body>
+</html>
diff --git a/layout/base/tests/test_dynamic_toolbar_max_height.html b/layout/base/tests/test_dynamic_toolbar_max_height.html
new file mode 100644
index 0000000000..abf0c34e8e
--- /dev/null
+++ b/layout/base/tests/test_dynamic_toolbar_max_height.html
@@ -0,0 +1,22 @@
+<!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(
+ {
+ set: [
+ ["dom.meta-viewport.enabled", true],
+ ],
+ },
+ function() {
+ // We need to open a new window because the API to set the dynamic toolbar
+ // max height works only in the top content document.
+ window.open("file_dynamic_toolbar_max_height.html");
+ }
+);
+</script>
+</html>
diff --git a/layout/base/tests/test_emulateMedium.html b/layout/base/tests/test_emulateMedium.html
new file mode 100644
index 0000000000..535904f0c0
--- /dev/null
+++ b/layout/base/tests/test_emulateMedium.html
@@ -0,0 +1,165 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=819930
+-->
+<meta charset="utf-8">
+<title>Test for Bug 819930</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/WindowSnapshot.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css" />
+<style>
+ @media braille {
+ body {
+ background-color: rgb(255, 255, 0);
+ }
+ }
+
+ @media embossed {
+ body {
+ background-color: rgb(210, 180, 140);
+ }
+ }
+
+ @media handheld {
+ body {
+ background-color: rgb(0, 255, 0);
+ }
+ }
+
+ @media print {
+ body {
+ background-color: rgb(0, 255, 255);
+ }
+ }
+
+ @media projection {
+ body {
+ background-color: rgb(30, 144, 255);
+ }
+ }
+
+ @media screen {
+ body {
+ background-color: green;
+ }
+ }
+
+ @media speech {
+ body {
+ background-color: rgb(192, 192, 192);
+ }
+ }
+
+ @media tty {
+ body {
+ background-color: rgb(255, 192, 203);
+ }
+ }
+
+ @media tv {
+ body {
+ background-color: rgb(75, 0, 130);
+ }
+ }
+</style>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=819930">Mozilla Bug 819930</a>
+<p id="display"></p>
+
+<div id="content" style="display: none"></div>
+
+<script>
+function waitForColorSchemeToBe(scheme) {
+ return new Promise(resolve => {
+ let mq = matchMedia(`(prefers-color-scheme: ${scheme})`);
+ if (mq.matches) {
+ resolve();
+ } else {
+ mq.addEventListener("change", resolve, { once: true });
+ }
+ });
+}
+
+add_setup(async function() {
+ // Set a dark color scheme so that we can properly test the print override.
+ await SpecialPowers.pushPrefEnv({ set: [["layout.css.prefers-color-scheme.content-override", 0]] });
+ await waitForColorSchemeToBe("dark");
+});
+
+add_task(function() {
+ let tests = [
+ {name: 'braille', value: 'rgb(255, 255, 0)'},
+ {name: 'embossed', value: 'rgb(210, 180, 140)'},
+ {name: 'handheld', value: 'rgb(0, 255, 0)'},
+ {name: 'print', value: 'rgb(0, 255, 255)'},
+ {name: 'projection', value: 'rgb(30, 144, 255)'},
+ {name: 'speech', value: 'rgb(192, 192, 192)'},
+ {name: 'tty', value: 'rgb(255, 192, 203)'},
+ {name: 'tv', value: 'rgb(75, 0, 130)'},
+ ];
+
+ let originalColor = 'rgb(0, 128, 0)';
+ let body = document.body;
+
+ let getColor = function() {
+ return window.getComputedStyle(body).backgroundColor;
+ };
+
+ for (let test of tests) {
+ // Emulate the given media
+ SpecialPowers.emulateMedium(window, test.name);
+ is(getColor(), test.value, 'emulating ' + test.name + ' produced ' +
+ 'correct rendering');
+
+ ok(matchMedia(test.name).matches, "Media matches");
+ if (test.value == "print") {
+ ok(matchMedia("(prefers-color-scheme: light)").matches, "color-scheme is overridden when emulating print");
+ }
+
+ // Do the @media screen rules get applied after ending the emulation?
+ SpecialPowers.stopEmulatingMedium(window);
+ is(getColor(), originalColor, 'Ending ' + test.name +
+ ' emulation restores style for original medium');
+ ok(!matchMedia(test.name).matches, "Media no longer matches");
+ ok(!matchMedia("(prefers-color-scheme: light)").matches, "color-scheme override should be restored");
+
+ // CSS media types are case-insensitive; we should be too.
+ SpecialPowers.emulateMedium(window, test.name.toUpperCase());
+ is(getColor(), test.value,
+ test.name + ' emulation is case-insensitive');
+ SpecialPowers.stopEmulatingMedium(window);
+ }
+
+ is(getColor(), originalColor, 'No emulation');
+
+ // Emulating screen should produce the same rendering as when there is
+ // no emulation in effect
+ SpecialPowers.emulateMedium(window, 'screen');
+ is(getColor(), originalColor, 'Emulating screen produces original rendering');
+ SpecialPowers.stopEmulatingMedium(window);
+
+ is(getColor(), originalColor, 'No emulation, shouldn\'t change');
+
+ // Screen should be case-insensitive too
+ SpecialPowers.emulateMedium(window, 'SCREEN');
+ is(getColor(), originalColor, 'screen emulation is case-insensitive');
+ SpecialPowers.stopEmulatingMedium(window);
+
+ is(getColor(), originalColor, 'No emulation, shouldn\'t change');
+
+ // An invalid parameter shouldn't fail. Given the CSS rules above,
+ // an invalid parameter should result in a different rendering from any
+ // produced thus far
+ SpecialPowers.emulateMedium(window, 'clay');
+ let invalid = getColor();
+ tests.push({name: 'screen', value: 'green'});
+ tests.forEach(function(test) {
+ isnot(invalid, test.value, 'Emulating invalid type differs from ' +
+ test.name);
+ });
+
+ SpecialPowers.stopEmulatingMedium(window);
+
+ is(getColor(), originalColor, 'No emulation, shouldn\'t change');
+})
+</script>
diff --git a/layout/base/tests/test_emulate_color_scheme.html b/layout/base/tests/test_emulate_color_scheme.html
new file mode 100644
index 0000000000..dce7171584
--- /dev/null
+++ b/layout/base/tests/test_emulate_color_scheme.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Emulation of color-scheme (bug 1570721)</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+<style>
+@media (prefers-color-scheme: light) {
+ #test { color: rgb(0, 1, 0); }
+}
+@media (prefers-color-scheme: dark) {
+ #test { color: rgb(0, 2, 0); }
+}
+</style>
+<div id="test"></div>
+<script>
+function colorId() {
+ // Gets the middle number of the rgb(0, x, 0) color.
+ let color = getComputedStyle(document.getElementById("test")).color;
+ let id = parseInt(color.split(",")[1], 10);
+ ok(id == 1 || id == 2 || id == 3, "Bogus color?");
+ return id;
+}
+
+{
+ let bc = SpecialPowers.wrap(window).browsingContext.top;
+ ok('prefersColorSchemeOverride' in bc, "API should exist");
+ is(bc.prefersColorSchemeOverride, "none", "Override shouldn't be active.");
+
+ let originalColor = colorId();
+
+ bc.prefersColorSchemeOverride = "light";
+ is(colorId(), 1, "Light emulation works");
+
+ bc.prefersColorSchemeOverride = "dark";
+ is(colorId(), 2, "Dark emulation works");
+
+ bc.prefersColorSchemeOverride = "none";
+ is(colorId(), originalColor, "Clearing the override works");
+}
+</script>
diff --git a/layout/base/tests/test_event_target_iframe_oop.html b/layout/base/tests/test_event_target_iframe_oop.html
new file mode 100644
index 0000000000..562433c955
--- /dev/null
+++ b/layout/base/tests/test_event_target_iframe_oop.html
@@ -0,0 +1,177 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=921928
+-->
+<head>
+ <title>Test for bug 921928</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>
+ #dialer {
+ position: absolute;
+ left: 0;
+ top: 0;
+ width: 100%;
+ height: 50px;
+ background: green;
+ }
+
+ #apps {
+ position: absolute;
+ left: 0;
+ top: 51px;
+ width: 100%;
+ height: 100px;
+ background: blue;
+ }
+
+ .hit {
+ position: absolute;
+ width: 3px;
+ height: 3px;
+ z-index: 20;
+ background: red;
+ border: 1px solid red;
+ }
+ </style>
+</head>
+<body id="body" style="margin:0; width:100%; height:100%">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var prefs = [
+ ["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 12],
+ ["ui.mouse.radius.topmm", 8],
+ ["ui.mouse.radius.rightmm", 4],
+ ["ui.mouse.radius.bottommm", 4],
+ ["ui.mouse.radius.visitedweight", 50],
+];
+
+var eventTarget;
+var debugHit = [];
+
+function endTest() {
+ SimpleTest.finish();
+ SpecialPowers.removePermission("browser", location.href);
+ for (var pref in prefs) {
+ SpecialPowers.pushPrefEnv({"clear": pref[0]}, function() {});
+ }
+}
+
+function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
+ eventTarget = null;
+ synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
+ try {
+ is(eventTarget.id, idTarget,
+ "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+ } catch (ex) {
+ ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
+ }
+}
+
+function showDebug() {
+ for (var i = 0; i < debugHit.length; i++) {
+ document.body.appendChild(debugHit[i]);
+ }
+
+ var screenshot = SpecialPowers.snapshotWindow(window, true);
+ dump('IMAGE:' + screenshot.toDataURL() + '\n');
+}
+
+/*
+ Setup the test environment: enabling event fluffing (all ui.* preferences),
+ and enabling remote process.
+*/
+function setupTest(cont) {
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({"set": prefs}, cont);
+}
+
+function execTest() {
+ /*
+ Creating two iframes that mimics the attention screen behavior on the
+ device:
+ - the 'dialer' iframe is the attention screen you have when a call is
+ in place. it is a green bar, so we copy it as green here too
+ - the 'apps' iframe mimics another application that is being run, be it
+ dialer, sms, ..., anything that the user might want to trigger during
+ a call
+
+ The bug we intent to reproduce here is that in this case, if the user taps
+ onto the top of the 'apps', the event fluffing code will in fact redirect
+ the event to the 'dialer' iframe. In practice, this is bug 921928 where
+ during a call the user wants to place a second call, and while typing the
+ phone number, wants to tap onto the 'delete' key to erase a digit, but ends
+ tapping and activating the dialer.
+ */
+ var dialer = document.createElement('iframe');
+ dialer.id = 'dialer';
+ dialer.src = '';
+ // Force OOP
+ dialer.setAttribute('mozbrowser', 'true');
+ dialer.setAttribute('remote', 'true');
+ document.body.appendChild(dialer);
+
+ var apps = document.createElement('iframe');
+ apps.id = 'apps';
+ apps.src = 'bug921928_event_target_iframe_apps_oop.html';
+ // Force OOP
+ apps.setAttribute('mozbrowser', 'true');
+ apps.setAttribute('remote', 'true');
+ document.body.appendChild(apps);
+
+ var handleEvent = function(event) {
+ eventTarget = event.target;
+
+ // We draw a small red div to show where the event has tapped
+ var hit = document.createElement('div');
+ hit.style.left = (event.clientX - 1.5) + 'px';
+ hit.style.top = (event.clientY - 1.5) + 'px';
+ hit.classList.add('hit');
+ debugHit.push(hit);
+ };
+
+ // In real life, the 'dialer' has a 'mousedown', so we mimic one too,
+ // to reproduce the same behavior
+ dialer.addEventListener('mousedown', function(e) {});
+
+ // This event listener is just here to record what iframe has been hit,
+ // and sets the 'eventTarget' to the iframe's id value so that the
+ // testMouseClick() code can correctly check. We cannot add it on the
+ // 'apps' otherwise it will alter the behavior of the test.
+ document.addEventListener('mousedown', handleEvent);
+
+ // In the following, the coordinates are relative to the iframe
+
+ // First, we check that tapping onto the 'dialer' correctly triggers the
+ // dialer.
+ testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with mouse input");
+ testMouseClick("dialer", 20, 1, "dialer", "correct hit on dialer with touch input", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+
+ // Now this is it: we tap inside 'apps', but very close to the border between
+ // 'apps' and 'dialer'. Without the fix from this bug, this test will fail.
+ testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for mouse input");
+ testMouseClick("apps", 20, 1, "apps", "apps <iframe mozbrowser remote> hit for touch input", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+
+ // Show small red spots of where the click happened
+ // showDebug();
+
+ endTest();
+}
+
+function runTest() {
+ setupTest(execTest);
+}
+
+addEventListener('load', function() { SimpleTest.executeSoon(runTest); });
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_event_target_radius.html b/layout/base/tests/test_event_target_radius.html
new file mode 100644
index 0000000000..caf046cf99
--- /dev/null
+++ b/layout/base/tests/test_event_target_radius.html
@@ -0,0 +1,422 @@
+<!DOCTYPE HTML>
+<html id="html" style="height:100%">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=780847
+-->
+<head>
+ <title>Test radii for mouse events</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>
+ .target { position:absolute; left:100px; top:100px; width:100px; height:100px; background:blue; }
+ </style>
+</head>
+<body id="body" onload="setTimeout(startTest, 0)" style="margin:0; width:100%; height:100%; overflow:hidden">
+<p id="display"></p>
+<div id="content">
+ <!-- We make the `t` target shorter than normal in case because we test the
+ bottom edge fluffing on this element, and the test page may be hosted
+ inside a short iframe in the test harness on some platforms.
+ -->
+ <div class="target" style="height:80px" id="t" onmousedown="x=1"></div>
+
+ <div class="target" id="t2" hidden></div>
+
+ <input class="target" id="t3_1" hidden></input>
+ <a href="#" class="target" id="t3_2" hidden></a>
+ <label class="target" id="t3_3" hidden></label>
+ <button class="target" id="t3_4" hidden></button>
+ <select class="target" id="t3_5" hidden></select>
+ <textarea class="target" id="t3_6" hidden></textarea>
+ <div role="button" class="target" id="t3_7" hidden></div>
+ <div role="key" class="target" id="t3_8" hidden></div>
+ <img class="target" id="t3_9" hidden></img>
+
+ <div class="target" style="transform:translate(-80px,0);" id="t4" onmousedown="x=1" hidden></div>
+
+ <div class="target" style="left:0; z-index:1" id="t5_left" onmousedown="x=1" hidden></div>
+ <div class="target" style="left:106px;" id="t5_right" onmousedown="x=1" hidden></div>
+ <div class="target" style="left:0; top:210px;" id="t5_below" onmousedown="x=1" hidden></div>
+
+ <div class="target" id="t6" onmousedown="x=1" style="width: 300px" hidden>
+ <div id="t6_inner" style="position:absolute; left:-40px; top:20px; width:60px; height:60px; background:yellow;"></div>
+ <div id="t6_inner_clickable" style="position:absolute; left:-40px; top: 80px; width: 60px; height: 5px; background:red" onmousedown="x=1"></div>
+ </div>
+ <div id="t6_outer" style="position:absolute; left:360px; top:120px; width:60px; height:60px; background:green;" onmousedown="x=1" hidden></div>
+
+ <div class="target" id="t7" onmousedown="x=1" hidden></div>
+ <div class="target" id="t7_over" hidden></div>
+
+ <div id="t8" contenteditable="true" class="target" hidden></div>
+
+ <div id="t9" class="target" ontouchend="x=1" hidden></div>
+
+ <div id="t10_left" class="target" style="left:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_right" class="target" style="left:auto;right:-50px" onmousedown="x=1" hidden></div>
+ <div id="t10_top" class="target" style="top:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_bottom" class="target" style="top:auto;bottom:-50px;" onmousedown="x=1" hidden></div>
+ <div id="t10_over" style="position:absolute; left:0; top:0; width:100%; height:100%; background:yellow;" hidden></div>
+
+ <div id="t11" class="target" style="cursor:pointer" hidden></div>
+ <div id="t11_with_child" class="target" style="cursor:pointer" hidden><div id="t11_child" style="width:100px; height:100px; background:green;"></div></div>
+ <div id="t11_covered" class="target" style="cursor:pointer" hidden><div id="t11_coverer" style="width:100px; height:100px; background:green; cursor:text"></div></div>
+
+ <div id="t12" class="target" hidden>
+ <input id="t12_input" style="width: 100px; height: 20px; border: 0; padding: 0"></input>
+ <div id="t12_zisland" style="width: 100px; height: 50px; position:relative; z-index: 5">
+ <div id="t12_target" style="width: 100px; height: 20px; background-color: green"></div>
+ </div>
+ </div>
+
+ <div id="t13" class="target" style="cursor:pointer" hidden>
+ <div id="t13_touchlistener" style="width: 50px; height: 50px; background:red" ontouchend="x=1"></div>
+ <div id="t13_notouchlistener" style="width: 50px; height: 50px; background:green"></div>
+ </div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.enabled", true],
+ ["ui.mouse.radius.inputSource.touchOnly", false],
+ ["ui.mouse.radius.leftmm", 12],
+ ["ui.mouse.radius.topmm", 4],
+ ["ui.mouse.radius.rightmm", 4],
+ ["ui.mouse.radius.bottommm", 4],
+ ["ui.mouse.radius.visitedweight", 50]]}, runTest);
+}
+
+
+SimpleTest.waitForExplicitFinish();
+
+let oldResolution = 1.0;
+
+function endTest() {
+ if (navigator.appVersion.includes("Android")) {
+ // Restore any resolution that was changed at the end of the test.
+ let topUtils = SpecialPowers.getDOMWindowUtils(window.top);
+ topUtils.setResolutionAndScaleTo(oldResolution);
+ }
+ SimpleTest.finish();
+}
+
+var eventTarget;
+window.onmousedown = function(event) { eventTarget = event.target; };
+
+function testMouseClick(idPosition, dx, dy, idTarget, msg, options) {
+ eventTarget = null;
+ synthesizeMouse(document.getElementById(idPosition), dx, dy, options || {});
+ try {
+ is(eventTarget.id, idTarget,
+ "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+ } catch (ex) {
+ ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + eventTarget);
+ }
+}
+
+var touchTarget;
+document.addEventListener('touchstart', event => { touchTarget = event.target; });
+
+function testTouch(idPosition, dx, dy, idTarget, msg, options) {
+ touchTarget = null;
+ synthesizeTouch(document.getElementById(idPosition), dx, dy, options || {});
+ try {
+ is(touchTarget.id, idTarget,
+ "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]");
+ } catch (ex) {
+ ok(false, "checking '" + idPosition + "' offset " + dx + "," + dy + " [" + msg + "]; got " + touchTarget);
+ }
+}
+
+function setShowing(id, show) {
+ var e = document.getElementById(id);
+ e.hidden = !show;
+}
+
+var mm;
+function runTest() {
+ let resolution = 1;
+ if (navigator.appVersion.includes("Android")) {
+ // Choose a roundish number for the resolution so that the "midpoint" test below
+ // doesn't mis-target an element due to rounding error.
+ let desiredResolution = 0.75;
+ let topUtils = SpecialPowers.getDOMWindowUtils(window.top);
+ // Save the old resolution for restoration at the end of the test.
+ oldResolution = topUtils.getResolution();
+ topUtils.setResolutionAndScaleTo(desiredResolution);
+ // This test runs on Android, zoomed out. Therefore we need to account
+ // for the resolution as well, because the fluff area is relative to screen
+ // pixels rather than CSS pixels.
+ resolution = topUtils.getResolution();
+ // Make sure we were actually able to zoom out to the desired level.
+ ok(resolution == desiredResolution, "Resolution is " + resolution);
+ }
+ mm = SpecialPowers.getDOMWindowUtils(window.top).physicalMillimeterInCSSPixels / resolution;
+ ok(4*mm >= 10, "WARNING: mm " + mm + " too small in this configuration. Test results will be bogus");
+
+ // Test basic functionality: clicks sufficiently close to the element
+ // should be allowed to hit the element. We test points just inside and
+ // just outside the edges we set up in the prefs.
+ testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality");
+ testMouseClick("t", 100 + 11*mm, 10, "t", "basic functionality");
+ testMouseClick("t", 10, 80 + 5*mm, "body", "basic functionality");
+ testMouseClick("t", 10, 80 + 3*mm, "t", "basic functionality");
+ testMouseClick("t", -5*mm, 10, "body", "basic functionality");
+ testMouseClick("t", -3*mm, 10, "t", "basic functionality");
+ testMouseClick("t", 10, -5*mm, "body", "basic functionality");
+ testMouseClick("t", 10, -3*mm, "t", "basic functionality");
+
+ // When inputSource.touchOnly is true, mouse input is not retargeted.
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", true]]}, test2);
+}
+
+function test2() {
+ testMouseClick("t", 100 + 11*mm, 10, "body", "disabled for mouse input");
+ testMouseClick("t", 100 + 11*mm, 10, "t", "enabled for touch input", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+ testMouseClick("t", 100 + 13*mm, 10, "body", "basic functionality for touch", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+ SpecialPowers.pushPrefEnv({"set": [["ui.mouse.radius.inputSource.touchOnly", false]]}, test3);
+}
+
+function test3() {
+ setShowing("t", false);
+
+ // Now test the criteria we use to determine which elements are hittable
+ // this way.
+
+ setShowing("t2", true);
+ var t2 = document.getElementById("t2");
+ // Unadorned DIVs are not click radius targets
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "unadorned DIV");
+ // DIVs with the right event handlers are click radius targets
+ t2.onmousedown = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmousedown");
+ t2.onmousedown = null;
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onmousedown removed");
+ t2.onmouseup = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onmouseup");
+ t2.onmouseup = null;
+ t2.onclick = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onclick");
+ t2.onclick = null;
+ t2.onpointerdown = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onpointerdown");
+ t2.onpointerdown = null;
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onpointerdown removed");
+ t2.onpointerup = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "t2", "DIV with onpointerup");
+ t2.onpointerup = null;
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onpointerup removed");
+ // Keypresses don't make click radius targets
+ t2.onkeypress = function() {};
+ testMouseClick("t2", 100 + 11*mm, 10, "body", "DIV with onkeypress");
+ t2.onkeypress = null;
+ setShowing("t2", false);
+
+ // Now check that certain elements are click radius targets and others are not
+ for (var i = 1; i <= 9; ++i) {
+ var id = "t3_" + i;
+ var shouldHit = i <= 8;
+ setShowing(id, true);
+ testMouseClick(id, 100 + 11*mm, 10, shouldHit ? id : "body",
+ "<" + document.getElementById(id).tagName + "> element");
+ setShowing(id, false);
+ }
+
+ // Check that our targeting computations take into account the effects of
+ // CSS transforms
+ setShowing("t4", true);
+ testMouseClick("t4", -1, 10, "t4", "translated DIV");
+ setShowing("t4", false);
+
+ // Test the prioritization of multiple targets based on distance to
+ // the target.
+ setShowing("t5_left", true);
+ setShowing("t5_right", true);
+ setShowing("t5_below", true);
+ testMouseClick("t5_left", 102, 10, "t5_left", "closest DIV is left");
+ testMouseClick("t5_left", 102.5, 10, "t5_left",
+ "closest DIV to midpoint is left because of its higher z-index");
+ testMouseClick("t5_left", 104, 10, "t5_right", "closest DIV is right");
+ testMouseClick("t5_left", 10, 104, "t5_left", "closest DIV is left");
+ testMouseClick("t5_left", 10, 105, "t5_left",
+ "closest DIV to midpoint is left because of its higher z-index");
+ testMouseClick("t5_left", 10, 106, "t5_below", "closest DIV is below");
+ setShowing("t5_left", false);
+ setShowing("t5_right", false);
+ setShowing("t5_below", false);
+
+ // Test behavior of nested elements.
+ // The following behaviors are questionable and may need to be changed.
+ setShowing("t6", true);
+ setShowing("t6_outer", true);
+ testMouseClick("t6_inner", -1, 10, "t6_inner",
+ "inner element is clickable because its parent is, even when it sticks outside parent");
+ testMouseClick("t6_inner", 39, -1, "t6_inner",
+ "when outside both inner and parent, but in range of both, the inner is selected");
+ testMouseClick("t6_inner", 45, -1, "t6_inner",
+ "clicking in clickable parent close to inner activates inner, not parent");
+ testMouseClick("t6_inner_clickable", 1, -1, "t6_inner",
+ "clicking on inner doesn't get redirected to inner_clickable because they are both clickable");
+ testMouseClick("t6_inner_clickable", 1, 1, "t6_inner_clickable",
+ "clicking on inner_clickable doesn't get redirected to inner because they are both clickable");
+ testMouseClick("t6_inner_clickable", 45, -1, "t6_inner",
+ "clicking on inner while backed by its parent still doesn't get redirected to inner_clickable");
+ testMouseClick("t6_inner_clickable", 45, 1, "t6_inner_clickable",
+ "clicking on inner_clickable while backed by its parent still doesn't get redirected to inner");
+ testMouseClick("t6_inner_clickable", 45, 6, "t6_inner_clickable",
+ "clicking on parent near inner_clickable gets redirected to inner_clickable rather than inner because it is closer");
+ // 280 is the distance from t6_inner's right edge to t6's right edge
+ // 240 is the distance from t6_inner's right edge to t6_outer's right edge.
+ // we want to click on t6, but at least 13mm away from t6_inner, so that
+ // t6_inner doesn't steal the click.
+ ok(13*mm < 280, "no point inside t6 that's not within radius of t6_inner; adjust layout of t6/inner/outer as needed");
+ testMouseClick("t6_outer", -240 + 13*mm, -1, "t6",
+ "clicking in clickable container close to outer activates parent, not outer");
+ testMouseClick("t6_outer", 1, 1, "t6_outer",
+ "clicking directly on the outer activates it");
+ setShowing("t6", false);
+ setShowing("t6_outer", false);
+
+ setShowing("t7", true);
+ setShowing("t7_over", true);
+ testMouseClick("t7", 100 + 11*mm, 10, "body", "covered div is not clickable");
+ testMouseClick("t7", 10, 10, "t7_over", "covered div is not clickable even within its bounds");
+ setShowing("t7", false);
+ setShowing("t7_over", false);
+
+ // Check that contenteditable elements are considered clickable for fluffing.
+ setShowing("t8", true);
+ var rect = document.getElementById("t8").getBoundingClientRect();
+ testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for mouse input");
+ testMouseClick("t8", rect.left + 1, rect.top + 1, "t8", "content editable enabled for touch input", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+ setShowing("t8", false);
+
+ // Check that elements are touchable
+ setShowing("t9", true);
+ var rect = document.getElementById("t9").getBoundingClientRect();
+ testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with mouse input");
+ testMouseClick("t9", rect.left + 1, rect.top + 1, "t9", "div enabled with touch input", {
+ inputSource: MouseEvent.MOZ_SOURCE_TOUCH
+ });
+ setShowing("t9", false);
+
+ setShowing("t10_over", true);
+ setShowing("t10_left", true);
+ setShowing("t10_right", true);
+ setShowing("t10_top", true);
+ setShowing("t10_bottom", true);
+ testMouseClick("t10_left", 51, 10, "t10_over", "element outside of visible area is not selected");
+ if (self.frameElement &&
+ (self.frameElement.offsetLeft + self.innerWidth >
+ SpecialPowers.wrap(top).innerWidth)) {
+ info("WARNING: Window is too narrow, can't test t10_right");
+ } else {
+ testMouseClick("t10_right", 49, 10, "t10_over", "element outside of visible area is not selected");
+ }
+ testMouseClick("t10_top", 10, 51, "t10_over", "element outside of visible area is not selected");
+ if (self.frameElement &&
+ (self.frameElement.offsetTop + self.innerHeight >
+ SpecialPowers.wrap(top).innerHeight)) {
+ info("WARNING: Window is too short, can't test t10_bottom");
+ } else {
+ testMouseClick("t10_bottom", 10, 49, "t10_over", "element outside of visible area is not selected");
+ }
+ setShowing("t10_over", false);
+ setShowing("t10_left", false);
+ setShowing("t10_right", false);
+ setShowing("t10_top", false);
+ setShowing("t10_bottom", false);
+
+ setShowing("t11", true);
+ testMouseClick("t11", 100 + 11*mm, 10, "t11",
+ "Elements with cursor:pointer are fluff targets");
+ setShowing("t11", false);
+
+ setShowing("t11_with_child", true);
+ testMouseClick("t11_with_child", 100 + 11*mm, 10, "t11_child",
+ "Elements that inherit cursor:pointer are fluff targets");
+ setShowing("t11_with_child", false);
+
+ setShowing("t11_covered", true);
+ testMouseClick("t11_covered", 100 + 11*mm, 10, "body",
+ "Elements that override an inherited cursor:pointer are not fluff targets");
+ setShowing("t11_covered", false);
+
+ setShowing("t12", true);
+ testMouseClick("t12_target", 1, 1, "t12_target",
+ "Event retargeting should not escape out from a z-index ancestor");
+ setShowing("t12", false);
+
+ // Click empty area of textarea
+ let textarea = document.getElementById("t3_6");
+ textarea.value = "foo bar baz\nfoo"
+ textarea.style.height = "3.3em";
+ textarea.style.lineHeight = "1.1";
+ setShowing("t3_6", true);
+ textarea.selectionStart = 0;
+ synthesizeMouseAtCenter(textarea, {});
+ is(textarea.selectionStart, textarea.value.length,
+ "selection should be set to last character");
+
+ textarea.value = ""
+ textarea.style.height = "auto";
+ textarea.style.lineHeight = "";
+ setShowing("t3_6", false);
+
+ // Not yet tested:
+ // -- visited link weight
+ // -- "Closest" using Euclidean distance
+
+
+ SpecialPowers.pushPrefEnv({"set": [["dom.w3c_touch_events.enabled", 1],
+ ["ui.touch.radius.enabled", true],
+ ["ui.touch.radius.leftmm", 12],
+ ["ui.touch.radius.topmm", 4],
+ ["ui.touch.radius.rightmm", 4],
+ ["ui.touch.radius.bottommm", 4],
+ ["ui.touch.radius.visitedweight", 50]]}, testTouchable);
+}
+
+function testTouchable() {
+ // Element "t" has a mousedown listener but no touch listener. So the touches
+ // that land immediately outside "t" should not hit "t"; only the touches
+ // directly aimed at "t" should hit it.
+ setShowing("t", true);
+ var rect = document.getElementById("t").getBoundingClientRect();
+ testTouch("t", rect.width - 1, 10, "t", "touch inside t right edge");
+ testTouch("t", rect.width + 1, 10, "body", "touch outside t right edge");
+ testTouch("t", 10, rect.height - 1, "t", "touch inside t bottom edge");
+ testTouch("t", 10, rect.height + 1, "body", "touch outside t bottom edge");
+ testTouch("t", 1, 10, "t", "touch inside t left edge");
+ testTouch("t", -1, 10, "body", "touch outside t left edge");
+ testTouch("t", 10, 1, "t", "touch inside t top edge");
+ testTouch("t", 10, -1, "body", "touch outside t top edge");
+ setShowing("t", false);
+
+ // Element "t9" has a touchend listener, so touches within the radius
+ // distance from it should hit it.
+ setShowing("t9", true);
+ testTouch("t9", -5*mm, 10, "body", "touch outside t9 left edge radius");
+ testTouch("t9", -3*mm, 10, "t9", "touch inside t9 left edge radius");
+ setShowing("t9", false);
+
+ // Element "t13" is clickable, so touches on descendants should not get retargeted.
+ // In particular, the touch that lands on t13_notouchlistener but within the touch radius
+ // of t13_touchlistener should not get retargeted.
+ setShowing("t13", true);
+ testTouch("t13", 10, 50 + (2*mm), "t13_notouchlistener", "touch outside t13_touchlistener bottom edge");
+ testTouch("t13", 10, 50 - (2*mm), "t13_touchlistener", "touch inside t13_touchlistener bottom edge");
+ setShowing("t13", false);
+
+ endTest();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_body_table.html b/layout/base/tests/test_frame_reconstruction_body_table.html
new file mode 100644
index 0000000000..b1f6af0481
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_body_table.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>
+ Test for Bug 1630819: Test we don't reframe the html element when
+ inserting a block element into a display:table body element.
+ </title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ const utils = SpecialPowers.getDOMWindowUtils(window);
+
+ function runTest() {
+ document.documentElement.offsetTop;
+
+ const frameCountBeforeReframe = utils.framesConstructed;
+
+ // We expect to construct one newly appended block, and reconstruct the
+ // display:table <body>, which consists of 8 frames including TableWrapper,
+ // Table, TableRowGroup, TableRow, TableColGroup, TableCol, TableCell, and
+ // TableCell's inner block.
+ const expectedFrameConstructionCount = 1 + 8;
+
+ let div = document.createElement("div");
+ document.body.appendChild(div);
+ document.documentElement.offsetTop;
+
+ is(utils.framesConstructed - frameCountBeforeReframe,
+ expectedFrameConstructionCount,
+ "We shouldn't reframe <html> when appending a <div> into a display:table <body>!");
+
+ SimpleTest.finish();
+ }
+ </script>
+
+ <style>
+ body {
+ display: table;
+ }
+ </style>
+
+ <body onload="runTest();"></body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_body_writing_mode.html b/layout/base/tests/test_frame_reconstruction_body_writing_mode.html
new file mode 100644
index 0000000000..70c7e3a0f0
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_body_writing_mode.html
@@ -0,0 +1,34 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>
+ Test for Bug 1593752: Test we don't reframe the html element when
+ inserting a canonical body element with the same writing-mode.
+ </title>
+ <link rel="author" title="Ting-Yu Lin" href="tlin@mozilla.com">
+ <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ const utils = SpecialPowers.getDOMWindowUtils(window);
+
+ function runTest() {
+ document.documentElement.offsetTop;
+
+ // We expect to construct only the canonical body because its writing-mode
+ // is the same as html's writing-mode.
+ const expectedFrameConstructCount = utils.framesConstructed + 1;
+ document.body.before(document.createElement("body"));
+ document.documentElement.offsetTop;
+
+ is(utils.framesConstructed, expectedFrameConstructCount,
+ "We should not reframe <html>!");
+
+ SimpleTest.finish();
+ }
+ </script>
+ <body onload="runTest();"></body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_for_column_span.html b/layout/base/tests/test_frame_reconstruction_for_column_span.html
new file mode 100644
index 0000000000..c368901241
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_for_column_span.html
@@ -0,0 +1,77 @@
+<!DOCTYPE html>
+<html>
+ <meta charset="utf-8">
+ <title>
+ Test for Bug 1503420: Test we don't reframe multi-column containing block
+ when appending a block containing a spanner kid.
+ </title>
+ <link rel="author" title="Ting-Yu Lin" href="mailto:tlin@mozilla.com">
+ <link rel="author" title="Mozilla" href="http://www.mozilla.org/">
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+
+ <script>
+ SimpleTest.waitForExplicitFinish();
+
+ const utils = SpecialPowers.getDOMWindowUtils(window);
+
+ function appendBlock() {
+ // Create a subtree like the following, and append it to columns.
+ // <div>
+ // <h3>spanner</h3>
+ // block2
+ // </div>
+ var spanner = document.createElement("h3");
+ var spannerText = document.createTextNode("spanner");
+ spanner.appendChild(spannerText);
+
+ var block2 = document.createElement("div");
+ var block2Text = document.createTextNode("block2");
+ block2.appendChild(spanner);
+ block2.appendChild(block2Text)
+
+ var column = document.getElementById("column");
+ column.appendChild(block2);
+ }
+
+ function runTest() {
+ document.documentElement.offsetTop;
+ // We expected to construct 6 more frames.
+ // 1) Block frame for <div>
+ // 2) Block frame for <h3>
+ // 3) Text frame for "spanner"
+ // 4) Text frame for "block2"
+ // 5) Column-span wrapper for <h3>, which is a sibling of <div>
+ // 6) Column-span wrapper for 5), which is a sibling of <article>
+ // Note: creating a continuation frame doesn't increase the count.
+ const expectedFrameConstructCount = utils.framesConstructed + 6;
+
+ appendBlock();
+ document.documentElement.offsetTop;
+
+ is(utils.framesConstructed, expectedFrameConstructCount,
+ "We shouldn't construct unexpected frames.");
+
+ SimpleTest.finish();
+ }
+ </script>
+
+ <style>
+ #column {
+ column-count: 3;
+ column-rule: 6px solid;
+ width: 400px;
+ outline: 1px solid black;
+ }
+ h3 {
+ column-span: all;
+ outline: 1px solid blue;
+ }
+ </style>
+
+ <body onload="runTest();">
+ <article id="column">
+ <div>block1</div>
+ </article>
+ </body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html b/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html
new file mode 100644
index 0000000000..f76e9f139f
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1110277
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1110277</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ .testspan {
+ color: yellow;
+ }
+ .testspan[attributestate],
+ .testspan[attributestate]::before, .testspan[attributestate]::after {
+ color: blue;
+ }
+
+ #firstlinetest::first-line {
+ color: purple;
+ }
+ #firstlinetest > .testspan::before {
+ content: "[*]";
+ }
+
+ #aftertest > .testspan::after {
+ content: "[*]";
+ }
+ </style>
+ <script type="application/javascript">
+
+ /** Test for Bug 1110277 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ runtest("first line test", "#firstlinetest > .testspan");
+ runtest("after test", "#aftertest > .testspan");
+ SimpleTest.finish();
+ }
+
+ function runtest(description, selector) {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ var span = document.querySelector(selector);
+ var cs = getComputedStyle(span, "");
+
+ var startcolor = cs.color;
+ var startcount = utils.framesConstructed;
+ is(startcolor, "rgb(255, 255, 0)", description + ": initial color");
+
+ span.setAttribute("attributestate", "true");
+
+ var endcolor = cs.color;
+ var endcount = utils.framesConstructed;
+ is(endcolor, "rgb(0, 0, 255)", description + ": final color");
+ is(endcount, startcount,
+ description + ": should not do frame construction")
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1110277">Mozilla Bug 1110277</a>
+<div id="firstlinetest">
+ <span class="testspan">This <span style="display:block">is a</span> test.</span>
+</div>
+<div id="aftertest">
+ <span class="testspan">This <span style="display:block">is a</span> test.</span>
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_for_svg_transforms.html b/layout/base/tests/test_frame_reconstruction_for_svg_transforms.html
new file mode 100644
index 0000000000..ac0b5d8191
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_for_svg_transforms.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1419764
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1419764</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <script type="application/javascript">
+
+ /** Test for Bug 1419764 **/
+
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ var utils = SpecialPowers.getDOMWindowUtils(window);
+ var rect = document.querySelector("rect");
+
+ var matrix = rect.transform.baseVal[0].matrix;
+
+ matrix.e = 100;
+ document.documentElement.offsetTop; // flush layout
+
+ var startcount = utils.framesConstructed;
+
+ matrix.e = 200;
+ document.documentElement.offsetTop; // flush layout
+
+ var endcount = utils.framesConstructed;
+ is(endcount, startcount, "should not do frame construction");
+ SimpleTest.finish();
+ }
+
+ </script>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1419764">Mozilla Bug 1419764</a>
+<svg>
+ <rect transform="translate(1 1)" width="20" height="20" fill="yellow"/>
+</svg>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_frame_reconstruction_scroll_restore.html b/layout/base/tests/test_frame_reconstruction_scroll_restore.html
new file mode 100644
index 0000000000..a5115bb694
--- /dev/null
+++ b/layout/base/tests/test_frame_reconstruction_scroll_restore.html
@@ -0,0 +1,82 @@
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1268195
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1268195</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ html, body {
+ margin: 0;
+ padding: 0;
+ }
+
+ .noscroll {
+ overflow: hidden;
+ height: 100%;
+ }
+
+ /* Toggling this on and off triggers a frame reconstruction on the <body> */
+ html.reconstruct-body::before {
+ top: 0;
+ content: '';
+ display: block;
+ height: 2px;
+ position: absolute;
+ width: 100%;
+ z-index: 99;
+ }
+ </style>
+ <script type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function run() {
+ // Make sure we have the right scroll element
+ SimpleTest.is(document.body.scrollTopMax > 0, true, "Body is the scrolling element");
+
+ // Scroll to the bottom
+ document.body.scrollTop = document.body.scrollTopMax;
+ SimpleTest.is(document.body.scrollTop > 0, true, "Scrolled body");
+
+ // Do a frame reconstruction on the body while also shortening the
+ // height, but still keep it long enough to be scrollable.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '1000px';
+ var reducedMax = document.body.scrollTopMax;
+ SimpleTest.is(document.body.scrollTop, reducedMax, `Scroll forced to new bottom ${reducedMax}`);
+
+ // Do another frame reconstruction while lengthening the height again.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '5000px';
+ SimpleTest.is(document.body.scrollTop, reducedMax, "Scroll remained at reduced height");
+
+ // Do a frame reconstruction on the body while also shortening the
+ // height, this time down to a non-scrollable height.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '1px';
+ SimpleTest.is(document.body.scrollTop, 0, "Scroll forced to top");
+
+ // Do another frame reconstruction while lengthening the height again.
+ document.body.classList.toggle('noscroll');
+ document.documentElement.classList.toggle('reconstruct-body');
+ document.getElementById('spacer').style.height = '5000px';
+ SimpleTest.is(document.body.scrollTop, 0, "Scroll remained at top");
+
+ SimpleTest.finish();
+ }
+ </script>
+</head>
+<body onload="setTimeout(run, 0)">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1268195">Mozilla Bug 1268195</a><br/>
+The scroll position should end the top of the page. This is the top, yay!
+<div id="spacer" style="height: 5000px"></div>
+The scroll position should end the top of the page. This is the bottom!
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html
new file mode 100644
index 0000000000..3b0431f458
--- /dev/null
+++ b/layout/base/tests/test_getBoxQuads_convertPointRectQuad.html
@@ -0,0 +1,717 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="startTest()">
+<p id="display"></p>
+<script>
+// Global variables we want eval() to be able to reference from anywhere
+var f1d;
+var text;
+var suppressedText;
+var suppressedText2;
+var comment;
+var fragment;
+var openedWindow;
+var zeroPoint;
+var zeroRect;
+var zeroQuad;
+var notInDocument = document.createElement('div');
+
+function isEval(expr, b) {
+ // we ignore an insignificant discrepancy in floating-point values
+ var exprVal = eval(expr);
+ if (exprVal != b && Math.abs(exprVal - b) < 0.0001) {
+ ok(true, expr + " (" + exprVal + " within 0.0001 of " + b + ")");
+ return;
+ }
+ is(exprVal, b, expr);
+}
+
+function isApprox(a, b, msg, options) {
+ if (a != b && 'tolerance' in options &&
+ Math.abs(a - b) < options.tolerance) {
+ ok(true, msg + "(" + a + " within " + options.tolerance + " of " + b + ")");
+ return;
+ }
+ is(a, b, msg);
+}
+
+function makeQuadsExpr(fromStr, options) {
+ var getBoxQuadsOptionParts = [];
+ if ('box' in options) {
+ getBoxQuadsOptionParts.push("box:'" + options.box + "'");
+ }
+ if ('toStr' in options) {
+ getBoxQuadsOptionParts.push("relativeTo:" + options.toStr);
+ }
+ return fromStr + ".getBoxQuads({" + getBoxQuadsOptionParts.join(',') + "})";
+}
+
+function makePointExpr(fromStr, options, x, y) {
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+ return ('toStr' in options ? options.toStr : "document") +
+ ".convertPointFromNode(new DOMPoint(" + x + "," + y + ")," + fromStr + ",{" +
+ convertPointOptionParts.join(",") + "})";
+}
+
+function checkConvertPoints(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].getBounds().width;
+ var boxHeight = selfQuads[0].getBounds().height;
+
+ var convertTopLeftPointExpr = makePointExpr(fromStr, options, 0, 0);
+ var topLeft = eval(convertTopLeftPointExpr);
+ isApprox(topLeft.x, x1, convertTopLeftPointExpr + ".x", options);
+ isApprox(topLeft.y, y1, convertTopLeftPointExpr + ".y", options);
+
+ var convertTopRightPointExpr = makePointExpr(fromStr, options, boxWidth, 0);
+ var topRight = eval(convertTopRightPointExpr);
+ isApprox(topRight.x, x2, convertTopRightPointExpr + ".x", options);
+ isApprox(topRight.y, y2, convertTopRightPointExpr + ".y", options);
+
+ var convertBottomRightPointExpr = makePointExpr(fromStr, options, boxWidth, boxHeight);
+ var bottomRight = eval(convertBottomRightPointExpr);
+ isApprox(bottomRight.x, x3, convertBottomRightPointExpr + ".x", options);
+ isApprox(bottomRight.y, y3, convertBottomRightPointExpr + ".y", options);
+
+ var convertBottomLeftPointExpr = makePointExpr(fromStr, options, 0, boxHeight);
+ var bottomLeft = eval(convertBottomLeftPointExpr);
+ isApprox(bottomLeft.x, x4, convertBottomLeftPointExpr + ".x", options);
+ isApprox(bottomLeft.y, y4, convertBottomLeftPointExpr + ".y", options);
+}
+
+function checkConvertRect(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].getBounds().width;
+ var boxHeight = selfQuads[0].getBounds().height;
+
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+
+ var convertRectExpr = ('toStr' in options ? options.toStr : "document") +
+ ".convertRectFromNode(new DOMRect(0,0," + boxWidth + "," + boxHeight + ")," +
+ fromStr + ",{" + convertPointOptionParts.join(",") + "})";
+ var quad = eval(convertRectExpr);
+ isApprox(quad.p1.x, x1, convertRectExpr + ".p1.x", options);
+ isApprox(quad.p1.y, y1, convertRectExpr + ".p1.y", options);
+ isApprox(quad.p2.x, x2, convertRectExpr + ".p2.x", options);
+ isApprox(quad.p2.y, y2, convertRectExpr + ".p2.y", options);
+ isApprox(quad.p3.x, x3, convertRectExpr + ".p3.x", options);
+ isApprox(quad.p3.y, y3, convertRectExpr + ".p3.y", options);
+ isApprox(quad.p4.x, x4, convertRectExpr + ".p4.x", options);
+ isApprox(quad.p4.y, y4, convertRectExpr + ".p4.y", options);
+}
+
+function checkConvertQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var selfQuads = eval(fromStr).getBoxQuads(
+ {box:options.box == "" ? "border" : options.box,
+ relativeTo:eval(fromStr)});
+ var boxWidth = selfQuads[0].getBounds().width;
+ var boxHeight = selfQuads[0].getBounds().height;
+
+ var convertPointOptionParts = [];
+ if ('box' in options) {
+ convertPointOptionParts.push("fromBox:'" + options.box + "'");
+ }
+ if ('toBox' in options) {
+ convertPointOptionParts.push("toBox:'" + options.toBox + "'");
+ }
+
+ var convertQuadExpr = ('toStr' in options ? options.toStr : "document") +
+ ".convertQuadFromNode(new DOMQuad(new DOMRect(0,0," + boxWidth + "," + boxHeight + "))," +
+ fromStr + ",{" + convertPointOptionParts.join(",") + "})";
+ var quad = eval(convertQuadExpr);
+ isApprox(quad.p1.x, x1, convertQuadExpr + ".p1.x", options);
+ isApprox(quad.p1.y, y1, convertQuadExpr + ".p1.y", options);
+ isApprox(quad.p2.x, x2, convertQuadExpr + ".p2.x", options);
+ isApprox(quad.p2.y, y2, convertQuadExpr + ".p2.y", options);
+ isApprox(quad.p3.x, x3, convertQuadExpr + ".p3.x", options);
+ isApprox(quad.p3.y, y3, convertQuadExpr + ".p3.y", options);
+ isApprox(quad.p4.x, x4, convertQuadExpr + ".p4.x", options);
+ isApprox(quad.p4.y, y4, convertQuadExpr + ".p4.y", options);
+}
+
+function checkQuadIsRect(fromStr, options, x, y, w, h) {
+ var quadsExpr = makeQuadsExpr(fromStr, options);
+ var quads = eval(quadsExpr);
+ is(quads.length, 1, quadsExpr + " checking quad count");
+ var q = quads[0];
+ isApprox(q.p1.x, x, quadsExpr + " checking quad.p1.x", options);
+ isApprox(q.p1.y, y, quadsExpr + " checking quad.p1.y", options);
+ isApprox(q.p2.x, x + w, quadsExpr + " checking quad.p2.x", options);
+ isApprox(q.p2.y, y, quadsExpr + " checking quad.p2.y", options);
+ isApprox(q.p3.x, x + w, quadsExpr + " checking quad.p3.x", options);
+ isApprox(q.p3.y, y + h, quadsExpr + " checking quad.p3.y", options);
+ isApprox(q.p4.x, x, quadsExpr + " checking quad.p4.x", options);
+ isApprox(q.p4.y, y + h, quadsExpr + " checking quad.p4.y", options);
+
+ isApprox(q.getBounds().left, x, quadsExpr + " checking quad.getBounds().left", options);
+ isApprox(q.getBounds().top, y, quadsExpr + " checking quad.getBounds().top", options);
+ isApprox(q.getBounds().width, w, quadsExpr + " checking quad.getBounds().width", options);
+ isApprox(q.getBounds().height, h, quadsExpr + " checking quad.getBounds().height", options);
+
+ checkConvertPoints(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+ checkConvertRect(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+ checkConvertQuad(fromStr, options, x, y, x + w, y, x + w, y + h, x, y + h);
+}
+
+function checkQuadIsQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4) {
+ var quadsExpr = makeQuadsExpr(fromStr, options);
+ var quads = eval(quadsExpr);
+ is(quads.length, 1, quadsExpr + " checking quad count");
+ var q = quads[0];
+ isApprox(q.p1.x, x1, quadsExpr + " checking quad.p1.x", options);
+ isApprox(q.p1.y, y1, quadsExpr + " checking quad.p1.y", options);
+ isApprox(q.p2.x, x2, quadsExpr + " checking quad.p2.x", options);
+ isApprox(q.p2.y, y2, quadsExpr + " checking quad.p2.y", options);
+ isApprox(q.p3.x, x3, quadsExpr + " checking quad.p3.x", options);
+ isApprox(q.p3.y, y3, quadsExpr + " checking quad.p3.y", options);
+ isApprox(q.p4.x, x4, quadsExpr + " checking quad.p4.x", options);
+ isApprox(q.p4.y, y4, quadsExpr + " checking quad.p4.y", options);
+
+ isApprox(q.getBounds().left, Math.min(x1,x2,x3,x4), quadsExpr + " checking quad.getBounds().left", options);
+ isApprox(q.getBounds().top, Math.min(y1,y2,y3,y4), quadsExpr + " checking quad.getBounds().top", options);
+ isApprox(q.getBounds().right, Math.max(x1,x2,x3,x4), quadsExpr + " checking quad.getBounds().right", options);
+ isApprox(q.getBounds().bottom, Math.max(y1,y2,y3,y4), quadsExpr + " checking quad.getBounds().bottom", options);
+
+ checkConvertPoints(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+ checkConvertRect(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+ checkConvertQuad(fromStr, options, x1, y1, x2, y2, x3, y3, x4, y4);
+}
+
+function checkException(expr, name) {
+ try {
+ eval(expr);
+ ok(false, "Exception should have been thrown for " + expr);
+ } catch (ex) {
+ is(ex.name, name, "Checking exception type for " + expr);
+ }
+}
+
+function checkNotFound(fromStr, toStr, x1, y1, x2, y2) {
+ var convertPointExpr = toStr + ".convertPointFromNode(new DOMPoint(" + x1 +
+ "," + y1 + ")," + fromStr + ")";
+ checkException(convertPointExpr, "NotFoundError");
+
+ var convertRectExpr = toStr + ".convertRectFromNode(new DOMRect(" + x1 +
+ "," + y1 + "," + x2 + "," + y2 + ")," + fromStr + ")";
+ checkException(convertRectExpr, "NotFoundError");
+
+ var convertQuadExpr = toStr + ".convertQuadFromNode(new DOMQuad(new DOMRect(" + x1 +
+ "," + y1 + "," + x2 + "," + y2 + "))," + fromStr + ")";
+ checkException(convertQuadExpr, "NotFoundError");
+}
+</script>
+<style>
+em {
+ display:inline-block; height:10px; background:gray;
+}
+</style>
+<div id="dContainer"
+ style="padding:13px 14px 15px 16px;
+ border-width:17px 18px 19px 20px; border-style:solid; border-color:yellow;
+ margin:21px 22px 23px 24px;">
+ <div id="d"
+ style="width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue;">
+ </div>
+</div>
+
+<div id="dUnrelated" style="width:50px; height:50px;"></div>
+
+<iframe id="f1" style="width:50px; height:50px; border:0; background:lime;"
+ src="file_getBoxQuads_convertPointRectQuad_frame1.html">
+</iframe>
+<!--
+It matters that the first part of this span is on the same line as the above <iframe>!
+That ensures the first quad's X position is not equal to the anonymous block's X position.
+-->
+<span id="ibSplit"
+ ><em id="ibSplitPart1" style="width:100px;"></em
+ ><div style="width:110px; height:20px; background:black"></div
+ ><em style="width:130px;"></em></span>
+
+<table cellspacing="0" id="table" style="border:0; margin:8px; padding:0; background:orange">
+ <tbody style="padding:0; margin:0; border:0; background:blue">
+ <tr style="height:50px; padding:0; margin:0; border:0">
+ <td style="border:0; margin:0; padding:0">Cell</td>
+ </tr>
+ </tbody>
+ <caption style="height:40px; background:yellow">Caption</caption>
+</table>
+
+<div style="height:80px; column-count:2; column-fill:auto; border:2px solid black;">
+ <div style="height:20px;"></div>
+ <div id="colSplit" style="height:80px; background:blue; border:10px solid red; border-bottom-width:15px"></div>
+</div>
+
+<div style="width:200px; border:2px solid black;"
+ ><em style="width:150px;"></em
+ ><span id="inlineSplit" style="background:pink; border:10px solid red; border-right-width:15px"
+ ><em style="width:20px; background:green"></em><em style="width:60px"></em
+ ></span
+></div>
+
+<div style="width:200px; border:2px solid black;"
+ ><em style="width:150px;"></em
+ ><span id="textContainer">T
+TextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextTextText</span
+></div>
+
+<div id="suppressedTextContainer"> </div>
+<div id="suppressedTextContainer2"> </div>
+
+<div id="commentContainer"><!-- COMMENT --></div>
+
+<div id="displayNone" style="display:none"></div>
+
+<div id="overflowHidden"
+ style="overflow:hidden; width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue;">
+ <div style="height:400px; background:lime;"></div>
+</div>
+
+<div id="overflowScroll"
+ style="overflow:scroll; width:120px; height:90px; padding:1px 2px 3px 4px;
+ border-width:5px 6px 7px 8px; border-style:solid; border-color:yellow;
+ margin:9px 10px 11px 12px; background:blue; background-clip:content-box;">
+ <div id="overflowScrollChild" style="height:400px;"></div>
+</div>
+
+<div id="scaleTransformContainer" style="width:200px; height:200px;">
+ <div id="scaleTransform"
+ style="transform:scale(2); transform-origin:top left; width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="translateTransformContainer" style="width:200px; height:200px;">
+ <div id="translateTransform"
+ style="transform:translate(30px,40px); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="rotateTransformContainer" style="width:200px; height:200px;">
+ <div id="rotateTransform"
+ style="transform:rotate(90deg); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="flipTransformContainer" style="width:200px; height:200px;">
+ <div id="flipTransform"
+ style="transform:scaleY(-1); width:70px; height:80px; background:yellow"></div>
+</div>
+
+<div id="rot45TransformContainer" style="width:200px; height:200px;">
+ <div id="rot45Transform"
+ style="transform:rotate(45deg); width:100px; height:100px; background:yellow"></div>
+</div>
+
+<div id="singularTransform" style="transform:scale(0); width:200px; height:200px;">
+ <div id="singularTransformChild1" style="height:50px;"></div>
+ <div id="singularTransformChild2" style="height:50px;"></div>
+</div>
+
+<div id="threeDTransformContainer" style="perspective:600px; width:200px; height:200px">
+ <div id="threeDTransform" style="transform:rotateY(70deg); background:yellow; height:100px; perspective:600px">
+ <div id="threeDTransformChild" style="transform:rotateY(-70deg); background:blue; height:50px;"></div>
+ </div>
+</div>
+
+<div id="preserve3DTransformContainer" style="perspective:600px; width:200px; height:200px">
+ <div id="preserve3DTransform" style="transform:rotateY(70deg); transform-style:preserve-3d; background:yellow; height:100px;">
+ <div id="preserve3DTransformChild" style="transform:rotateY(-70deg); background:blue; height:50px;"></div>
+ </div>
+</div>
+
+<div id="svgContainer">
+ <svg id="svg" style="width:200px; height:200px; background:lightgray; border:7px solid blue; padding:4px">
+ <circle id="circle" cx="50" cy="50" r="20" fill="red" style="margin:20px; padding:10px; border:15px solid black"></circle>
+ <g transform="scale(2)">
+ <foreignObject x="50" y="20">
+ <div id="foreign" style="width:100px; height:60px; background:purple"></div>
+ </foreignObject>
+ </g>
+ </svg>
+</div>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+window.scrollTo(0,0);
+
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set": [["layout.css.getBoxQuads.enabled", true],
+ ["layout.css.convertFromNode.enabled", true]]}, grabFeatures);
+}
+
+// This is a bit of a hack but it works. Our Window object was set up while
+// the prefs might have been false so it may not have the features we're
+// testing. Create an <iframe> whose Window is initialized while the prefs are
+// true, and we can steal the features from that Window.
+// When these prefs are enabled on all builds by default, we can skip this step.
+function grabFeatures() {
+ var x = document.createElement('iframe');
+ x.src = "about:blank";
+ document.body.appendChild(x);
+ function setupFeatures(w) {
+ for (var name of ["getBoxQuads", "convertQuadFromNode", "convertRectFromNode", "convertPointFromNode"]) {
+ w.Text.prototype[name] = x.contentWindow.Text.prototype[name];
+ w.Element.prototype[name] = x.contentWindow.Element.prototype[name];
+ w.Document.prototype[name] = x.contentWindow.Document.prototype[name];
+ }
+ for (var name of ["DOMPoint", "DOMQuad"]) {
+ w[name] = x.contentWindow[name];
+ }
+ }
+ x.onload = function() {
+ setupFeatures(window);
+ setupFeatures(f1.contentWindow);
+ runTest();
+ };
+}
+
+function runTest() {
+ zeroPoint = new DOMPoint(0,0);
+ zeroRect = new DOMRect(0,0,0,0);
+ zeroQuad = new DOMQuad(zeroRect);
+
+ // Setup globals
+ f1d = f1.contentWindow.f1d;
+ text = textContainer.firstChild;
+ suppressedText = suppressedTextContainer.firstChild;
+ suppressedText2 = suppressedTextContainer2.firstChild;
+ comment = commentContainer.firstChild;
+ fragment = document.createDocumentFragment();
+
+ // Test basic BoxQuadOptions.box.
+ var dX = d.getBoundingClientRect().left;
+ var dY = d.getBoundingClientRect().top;
+ var dW = d.getBoundingClientRect().width;
+ var dH = d.getBoundingClientRect().height;
+
+ checkQuadIsRect("d", {box:"content"},
+ dX + 4 + 8, dY + 1 + 5, 120, 90);
+ checkQuadIsRect("d", {box:"padding"},
+ dX + 8, dY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("d", {box:"border"},
+ dX, dY, dW, dH);
+ checkQuadIsRect("d", {},
+ dX, dY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("d", {box:"margin"},
+ dX - 12, dY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test basic BoxQuadOptions.relativeTo
+ checkQuadIsRect("d", {toStr:"dContainer"},
+ 12 + 16 + 20, 9 + 13 + 17, dW, dH);
+
+ // Test BoxQuadOptions.relativeTo relative to this document
+ checkQuadIsRect("d", {toStr:"document"},
+ dX, dY, dW, dH);
+ // Test BoxQuadOptions.relativeTo relative to a non-ancestor.
+ var dUnrelatedX = dUnrelated.getBoundingClientRect().left;
+ var dUnrelatedY = dUnrelated.getBoundingClientRect().top;
+ checkQuadIsRect("d", {toStr:"dUnrelated"},
+ dX - dUnrelatedX, dY - dUnrelatedY, dW, dH);
+ // Test BoxQuadOptions.relativeTo relative to an element in a different document (and the document)
+ var f1X = f1.getBoundingClientRect().left;
+ var f1Y = f1.getBoundingClientRect().top;
+ checkQuadIsRect("d", {toStr:"f1.contentWindow.f1d"},
+ dX - (f1X + 14), dY - (f1Y + 15), dW, dH);
+ checkQuadIsRect("d", {toStr:"f1.contentDocument"},
+ dX - f1X, dY - f1Y, dW, dH);
+ // Test one document relative to another
+ checkQuadIsRect("f1.contentDocument", {toStr:"document"},
+ f1X, f1Y, 50, 50);
+ // The box type is irrelevant for a document
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"content"},
+ f1X, f1Y, 50, 50);
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"margin"},
+ f1X, f1Y, 50, 50);
+ checkQuadIsRect("f1.contentDocument", {toStr:"document",box:"padding"},
+ f1X, f1Y, 50, 50);
+
+ // Test that anonymous boxes are correctly ignored when building quads.
+ var ibSplitPart1X = ibSplitPart1.getBoundingClientRect().left;
+ var ibSplitY = ibSplit.getBoundingClientRect().top;
+ isEval("ibSplit.getBoxQuads().length", 3);
+ isEval("ibSplit.getBoxQuads()[0].getBounds().left", ibSplitPart1X);
+ isEval("ibSplit.getBoxQuads()[0].getBounds().width", 100);
+ isEval("ibSplit.getBoxQuads()[1].getBounds().width", 110);
+ isEval("ibSplit.getBoxQuads()[2].getBounds().width", 130);
+ isEval("table.getBoxQuads().length", 2);
+ isEval("table.getBoxQuads()[0].getBounds().height", 50);
+ isEval("table.getBoxQuads()[1].getBounds().height", 40);
+
+ // Test that we skip anonymous boxes when finding the right box to be relative to.
+ checkQuadIsRect("d", {toStr:"ibSplit", tolerance:0.0001},
+ dX - ibSplitPart1X, dY - ibSplitY, dW, dH);
+ var tableX = table.getClientRects()[0].left;
+ var tableY = table.getClientRects()[0].top;
+ checkQuadIsRect("d", {toStr:"table"},
+ dX - tableX, dY - tableY, dW, dH);
+ isEval("ibSplit.convertPointFromNode(zeroPoint,d).x", dX - ibSplitPart1X);
+ isEval("table.convertPointFromNode(zeroPoint,d).x", dX - table.getClientRects()[0].left);
+
+ // Test boxes generated by block splitting. Check for borders being placed correctly.
+ var colSplitY = colSplit.getClientRects()[0].top;
+ isEval("colSplit.getBoxQuads().length", 2);
+ isEval("colSplit.getBoxQuads()[0].getBounds().top", colSplitY);
+ isEval("colSplit.getBoxQuads()[0].getBounds().height", 60);
+ isEval("colSplit.getBoxQuads()[1].getBounds().top", colSplitY - 20);
+ isEval("colSplit.getBoxQuads()[1].getBounds().height", 45);
+ isEval("colSplit.getBoxQuads({box:'content'}).length", 2);
+ // The first box for the block has the top border; the second box has the bottom border.
+ isEval("colSplit.getBoxQuads({box:'content'})[0].getBounds().top", colSplitY + 10);
+ isEval("colSplit.getBoxQuads({box:'content'})[0].getBounds().height", 50);
+ isEval("colSplit.getBoxQuads({box:'content'})[1].getBounds().top", colSplitY - 20);
+ isEval("colSplit.getBoxQuads({box:'content'})[1].getBounds().height", 30);
+
+ var inlineSplitX = inlineSplit.getClientRects()[0].left;
+ isEval("inlineSplit.getBoxQuads().length", 2);
+ isEval("inlineSplit.getBoxQuads()[0].getBounds().left", inlineSplitX);
+ isEval("inlineSplit.getBoxQuads()[0].getBounds().width", 30);
+ isEval("inlineSplit.getBoxQuads()[1].getBounds().left", inlineSplitX - 150);
+ isEval("inlineSplit.getBoxQuads()[1].getBounds().width", 75);
+ isEval("inlineSplit.getBoxQuads({box:'content'}).length", 2);
+ // The first box for the inline has the left border; the second box has the right border.
+ isEval("inlineSplit.getBoxQuads({box:'content'})[0].getBounds().left", inlineSplitX + 10);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[0].getBounds().width", 20);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[1].getBounds().left", inlineSplitX - 150);
+ isEval("inlineSplit.getBoxQuads({box:'content'})[1].getBounds().width", 60);
+
+ var textX = textContainer.getClientRects()[0].left;
+ isEval("text.getBoxQuads().length", 2);
+ isEval("text.getBoxQuads()[0].getBounds().left", textX);
+ isEval("text.getBoxQuads()[1].getBounds().left", textX - 150);
+ // Box types are irrelevant for text
+ isEval("text.getBoxQuads({box:'content'}).length", 2);
+ isEval("text.getBoxQuads({box:'content'})[0].getBounds().left", textX);
+ isEval("text.getBoxQuads({box:'content'})[1].getBounds().left", textX - 150);
+ isEval("text.getBoxQuads({box:'padding'}).length", 2);
+ isEval("text.getBoxQuads({box:'padding'})[0].getBounds().left", textX);
+ isEval("text.getBoxQuads({box:'padding'})[1].getBounds().left", textX - 150);
+ isEval("text.getBoxQuads({box:'margin'}).length", 2);
+ isEval("text.getBoxQuads({box:'margin'})[0].getBounds().left", textX);
+ isEval("text.getBoxQuads({box:'margin'})[1].getBounds().left", textX - 150);
+
+ // Test table margins
+ isEval("table.getBoxQuads({box:'margin'}).length", 1);
+ isEval("table.getBoxQuads({box:'margin'})[0].getBounds().height", 106);
+
+ // Check that a text node whose layout might have been optimized away gives
+ // correct results.
+ var suppressedTextContainerX = suppressedTextContainer.getBoundingClientRect().left;
+ isEval("suppressedText.getBoxQuads().length", 1);
+ isEval("suppressedText.getBoxQuads()[0].getBounds().left", suppressedTextContainerX);
+ isEval("suppressedText.getBoxQuads()[0].getBounds().width", 0);
+
+ var suppressedTextContainer2X = suppressedTextContainer2.getBoundingClientRect().left;
+ isEval("document.convertPointFromNode(zeroPoint,suppressedText2).x",
+ suppressedTextContainer2X);
+
+ checkException("comment.getBoxQuads()", "TypeError");
+ checkException("d.getBoxQuads({relativeTo:comment})", "TypeError");
+ checkException("comment.convertPointFromNode(zeroPoint,document)", "TypeError");
+ checkException("document.convertPointFromNode(zeroPoint,comment)", "TypeError");
+ checkException("comment.convertRectFromNode(zeroRect,document)", "TypeError");
+ checkException("document.convertRectFromNode(zeroRect,comment)", "TypeError");
+ checkException("comment.convertQuadFromNode(zeroQuad,document)", "TypeError");
+ checkException("document.convertQuadFromNode(zeroQuad,comment)", "TypeError");
+
+ checkException("fragment.getBoxQuads()", "TypeError");
+ checkException("d.getBoxQuads({relativeTo:fragment})", "TypeError");
+ checkException("fragment.convertPointFromNode(zeroPoint,document)", "TypeError");
+ checkException("document.convertPointFromNode(zeroPoint,fragment)", "TypeError");
+ checkException("fragment.convertRectFromNode(zeroRect,document)", "TypeError");
+ checkException("document.convertRectFromNode(zeroRect,fragment)", "TypeError");
+ checkException("fragment.convertQuadFromNode(zeroQuad,document)", "TypeError");
+ checkException("document.convertQuadFromNode(zeroQuad,fragment)", "TypeError");
+
+ isEval("displayNone.getBoxQuads().length", 0);
+ isEval("notInDocument.getBoxQuads().length", 0);
+ checkNotFound("displayNone", "document", 1, 2, 3, 4);
+ checkNotFound("notInDocument", "document", 1, 2, 3, 4);
+ checkNotFound("document", "displayNone", 1, 2, 3, 4);
+ checkNotFound("document", "notInDocument", 1, 2, 3, 4);
+
+ // Test an overflow:hidden version of d. overflow:hidden should not affect
+ // the quads, basically.
+ var oHX = overflowHidden.getBoundingClientRect().left;
+ var oHY = overflowHidden.getBoundingClientRect().top;
+ checkQuadIsRect("overflowHidden", {box:"content"},
+ oHX + 4 + 8, oHY + 1 + 5, 120, 90);
+ checkQuadIsRect("overflowHidden", {box:"padding"},
+ oHX + 8, oHY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("overflowHidden", {box:"border"},
+ oHX, oHY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowHidden", {},
+ oHX, oHY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowHidden", {box:"margin"},
+ oHX - 12, oHY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test an overflow:scroll version of d. I assume that boxes aren't affected
+ // by the scrollbar although it's not clear that this is correct.
+ var oSX = overflowScroll.getBoundingClientRect().left;
+ var oSY = overflowScroll.getBoundingClientRect().top;
+ checkQuadIsRect("overflowScroll", {box:"content"},
+ oSX + 4 + 8, oSY + 1 + 5, 120, 90);
+ checkQuadIsRect("overflowScroll", {box:"padding"},
+ oSX + 8, oSY + 5, 120 + 2 + 4, 90 + 1 + 3);
+ checkQuadIsRect("overflowScroll", {box:"border"},
+ oSX, oSY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowScroll", {},
+ oSX, oSY, 120 + 2 + 4 + 6 + 8, 90 + 1 + 3 + 5 + 7);
+ checkQuadIsRect("overflowScroll", {box:"margin"},
+ oSX - 12, oSY - 9, 120 + 2 + 4 + 6 + 8 + 10 + 12, 90 + 1 + 3 + 5 + 7 + 9 + 11);
+
+ // Test simple 2D transforms.
+ var stcX = scaleTransformContainer.getBoundingClientRect().left;
+ var stcY = scaleTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("scaleTransform", {},
+ stcX, stcY, 140, 160);
+ var ttcX = translateTransformContainer.getBoundingClientRect().left;
+ var ttcY = translateTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("translateTransform", {},
+ ttcX + 30, ttcY + 40, 70, 80);
+ // Test mapping into a transformed element.
+ checkQuadIsRect("scaleTransform", {toStr:"translateTransform"},
+ stcX - (ttcX + 30), stcY - (ttcY + 40), 140, 160);
+ // Test 90 degree rotation.
+ var rotatetcX = rotateTransformContainer.getBoundingClientRect().left;
+ var rotatetcY = rotateTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("rotateTransform", {},
+ rotatetcX + 75, rotatetcY + 5,
+ rotatetcX + 75, rotatetcY + 75,
+ rotatetcX - 5, rotatetcY + 75,
+ rotatetcX - 5, rotatetcY + 5);
+ // Test vertical flip.
+ var fliptcX = flipTransformContainer.getBoundingClientRect().left;
+ var fliptcY = flipTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("flipTransform", {},
+ fliptcX, fliptcY + 80,
+ fliptcX + 70, fliptcY + 80,
+ fliptcX + 70, fliptcY,
+ fliptcX, fliptcY);
+ // Test non-90deg rotation.
+ var rot45tcX = rot45TransformContainer.getBoundingClientRect().left;
+ var rot45tcY = rot45TransformContainer.getBoundingClientRect().top;
+ var halfDiagonal = 100/Math.sqrt(2);
+ checkQuadIsQuad("rot45Transform", {tolerance:0.01},
+ rot45tcX + 50, rot45tcY + 50 - halfDiagonal,
+ rot45tcX + 50 + halfDiagonal, rot45tcY + 50,
+ rot45tcX + 50, rot45tcY + 50 + halfDiagonal,
+ rot45tcX + 50 - halfDiagonal, rot45tcY + 50);
+
+ // Test singular transforms.
+ var singularTransformX = singularTransform.getBoundingClientRect().left;
+ var singularTransformY = singularTransform.getBoundingClientRect().top;
+ // They map everything to a point.
+ checkQuadIsRect("singularTransform", {},
+ singularTransformX, singularTransformY, 0, 0);
+ checkQuadIsRect("singularTransformChild2", {},
+ singularTransformX, singularTransformY, 0, 0);
+ // Mapping into an element with a singular transform from outside sets
+ // everything to zero.
+ checkQuadIsRect("d", {toStr:"singularTransform"},
+ 0, 0, 0, 0);
+ // But mappings within a subtree of an element with a singular transform work.
+ checkQuadIsRect("singularTransformChild2", {toStr:"singularTransformChild1"},
+ 0, 50, 200, 50);
+
+ // Test 3D transforms.
+ var t3tcX = threeDTransformContainer.getBoundingClientRect().left;
+ var t3tcY = threeDTransformContainer.getBoundingClientRect().top;
+ checkQuadIsQuad("threeDTransform", {tolerance:0.01},
+ t3tcX + 59.446714, t3tcY - 18.569847,
+ t3tcX + 129.570778, t3tcY + 13.540874,
+ t3tcX + 129.570778, t3tcY + 100,
+ t3tcX + 59.446714, t3tcY + 100);
+ // Test nested 3D transforms (without preserve-3d).
+ checkQuadIsQuad("threeDTransformChild", {tolerance:0.01},
+ t3tcX + 89.395061, t3tcY + 2.243033,
+ t3tcX + 113.041727, t3tcY - 2.758530,
+ t3tcX + 113.041727, t3tcY + 52.985921,
+ t3tcX + 89.395061, t3tcY + 47.571899);
+ // Test preserve-3D.
+ var p3dtcX = preserve3DTransformContainer.getBoundingClientRect().left;
+ var p3dtcY = preserve3DTransformContainer.getBoundingClientRect().top;
+ checkQuadIsRect("preserve3DTransformChild", {tolerance:0.01},
+ p3dtcX, p3dtcY, 200, 50,
+ {tolerance:0.0001});
+ // Test mapping back into preserve-3D.
+ checkQuadIsRect("d", {toStr:"preserve3DTransformChild",tolerance:0.01},
+ dX - p3dtcX, dY - p3dtcY, dW, dH);
+
+ // Test SVG.
+ var svgContainerX = svgContainer.getBoundingClientRect().left;
+ var svgContainerY = svgContainer.getBoundingClientRect().top;
+ checkQuadIsRect("circle", {},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ // Box types are ignored for SVG elements.
+ checkQuadIsRect("circle", {box:"content"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("circle", {box:"padding"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("circle", {box:"margin"},
+ svgContainerX + 41, svgContainerY + 41, 40, 40);
+ checkQuadIsRect("d", {toStr:"circle"},
+ dX - (svgContainerX + 41), dY - (svgContainerY + 41), dW, dH);
+ // Test foreignObject inside an SVG transform.
+ checkQuadIsRect("foreign", {},
+ svgContainerX + 111, svgContainerY + 51, 200, 120);
+ // Outer <svg> elements support padding and content boxes
+ checkQuadIsRect("svg", {box:"border"},
+ svgContainerX, svgContainerY, 222, 222);
+ checkQuadIsRect("svg", {box:"padding"},
+ svgContainerX + 7, svgContainerY + 7, 208, 208);
+ checkQuadIsRect("svg", {box:"content"},
+ svgContainerX + 11, svgContainerY + 11, 200, 200);
+
+ // XXX Test SVG text (probably broken; unclear what the best way is to handle it)
+
+ // Test that converting between nodes in different toplevel browsing contexts
+ // throws an exception.
+ try {
+ openedWindow = window.open("file_getBoxQuads_convertPointRectQuad_frame2.html","");
+ } catch (ex) {
+ // in some cases we can't open the window.
+ openedWindow = null;
+ }
+ if (openedWindow) {
+ openedWindow.addEventListener("load", function() {
+ checkException("openedWindow.d.getBoxQuads({relativeTo:document})", "NotFoundError");
+ checkException("document.getBoxQuads({relativeTo:openedWindow.d})", "NotFoundError");
+ checkException("openedWindow.d.convertPointFromNode(zeroPoint,document)", "NotFoundError");
+ checkException("document.convertPointFromNode(zeroPoint,openedWindow.d)", "NotFoundError");
+ checkException("openedWindow.d.convertRectFromNode(zeroRect,document)", "NotFoundError");
+ checkException("document.convertRectFromNode(zeroRect,openedWindow.d)", "NotFoundError");
+ checkException("openedWindow.d.convertQuadFromNode(zeroQuad,document)", "NotFoundError");
+ checkException("document.convertQuadFromNode(zeroQuad,openedWindow.d)", "NotFoundError");
+ openedWindow.close();
+ SimpleTest.finish();
+ });
+ } else {
+ SimpleTest.finish();
+ }
+}
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_getClientRects_emptytext.html b/layout/base/tests/test_getClientRects_emptytext.html
new file mode 100644
index 0000000000..3717beb0ae
--- /dev/null
+++ b/layout/base/tests/test_getClientRects_emptytext.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <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: none">
+</div>
+<div id="testDiv"> </div>
+<script>
+var textNode = testDiv.firstChild;
+var range = new Range();
+range.selectNodeContents(textNode);
+is(range.getClientRects().length, 1, "Text node should have a rectangle");
+var rect = range.getClientRects()[0];
+ok(rect.left > 0, "Rectangle x should be greater than zero");
+ok(rect.top > 0, "Rectangle y should be greater than zero");
+is(rect.width, 0, "Rectangle should be zero width");
+is(rect.height, 0, "Rectangle should be zero height");
+</script>
+</body>
+</html>
diff --git a/layout/base/tests/test_mozPaintCount.html b/layout/base/tests/test_mozPaintCount.html
new file mode 100644
index 0000000000..dcf8eef3d2
--- /dev/null
+++ b/layout/base/tests/test_mozPaintCount.html
@@ -0,0 +1,62 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Tests for DOMWindowUtils.paintCount</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body onload="doBackgroundFlicker()">
+<p id="display">
+<div width="100" height="100" id="p" style="background-color: rgb(0,0,0)"/>
+</p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var startPaintCount = SpecialPowers.DOMWindowUtils.paintCount;
+ok(true, "Got to initial paint count: " + startPaintCount);
+var color = 0;
+
+function doElementFlicker() {
+ ok(true, "Plugin color iteration " + color + ", paint count: " + SpecialPowers.DOMWindowUtils.paintCount);
+ if (SpecialPowers.DOMWindowUtils.paintCount - startPaintCount > 20) {
+ ok(true, "Got enough paints from plugin color changes");
+ SimpleTest.finish();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.getElementById("p").style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doElementFlicker, 0);
+}
+
+function doBackgroundFlicker() {
+ ok(true, "Background color iteration " + color + ", paint count: " + SpecialPowers.DOMWindowUtils.paintCount);
+ if (SpecialPowers.DOMWindowUtils.paintCount - startPaintCount > 20) {
+ ok(true, "Got enough paints from background color changes");
+ startPaintCount = SpecialPowers.DOMWindowUtils.paintCount;
+ doElementFlicker();
+ return;
+ }
+
+ color = (color + 1) % 256;
+ document.body.style.backgroundColor = "rgb(" + color + "," + color + "," + color + ")";
+ setTimeout(doBackgroundFlicker, 0);
+}
+
+</script>
+</pre>
+
+<div style="height:4000px"></div>
+<a id="first" href="http://www.mozilla.org/">first<br>link</a>
+<a id="second" href="http://www.mozilla.org/">second link</a>
+<a id="third" href="http://www.mozilla.org/">third<br>link</a>
+<div style="height:4000px"></div>
+
+</body>
+</html>
+
diff --git a/layout/base/tests/test_partialbg.html b/layout/base/tests/test_partialbg.html
new file mode 100644
index 0000000000..8c5b6b466a
--- /dev/null
+++ b/layout/base/tests/test_partialbg.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1231622
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1231622: Draw partial frames of downloading css background images</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="SimpleTest.waitForFocus(runTest)">
+
+<style>
+div {
+ width: 200px;
+ height: 200px;
+ background-size: 200px 200px; background-image: url(sendimagenevercomplete.sjs)
+}
+</style>
+<script>
+/* sendimagenevercomplete.sjs sends us a partial png file and keeps the
+ * connection open but sends no more data. This is enough data to draw at last
+ * a partial frame. We do this so that we can distinguish from drawing a
+ * partial frame after we've been told all data has arrived (what we do even
+ * without the pref layout.display_partial_background_images turned on), from
+ * drawing a partial frame while data is still arriving (what we want to do).
+ */
+
+SimpleTest.waitForExplicitFinish();
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+function checkPixel(r, x, y, red, green, blue, alpha) {
+ let canvas = snapshotRect(window, r);
+ let context = canvas.getContext('2d');
+
+ let image = context.getImageData(x, y, 1, 1);
+ if (image.data[0] == red &&
+ image.data[1] == green &&
+ image.data[2] == blue &&
+ image.data[3] == alpha) {
+ return true;
+ }
+ return false;
+}
+
+async function runTest() {
+ await SpecialPowers.pushPrefEnv({'set': [['layout.display_partial_background_images', true]]});
+
+ let theDiv = document.createElement("div");
+ document.body.appendChild(theDiv);
+
+ let r = theDiv.getBoundingClientRect();
+
+ // Give that some time to partially load.
+ for (let i = 0; i < 10; i++) {
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+
+ let correct = false;
+ while (!correct) {
+ // Check the middle pixel part way down the partial frame.
+ correct = checkPixel(r, 100, 25, 0, 0, 255, 255);
+
+ await new Promise(resolve => requestAnimationFrame(resolve));
+ }
+
+ ok(correct, "correct pixel value");
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_preserve3d_sorting_hit_testing.html b/layout/base/tests/test_preserve3d_sorting_hit_testing.html
new file mode 100644
index 0000000000..2b975a0ef6
--- /dev/null
+++ b/layout/base/tests/test_preserve3d_sorting_hit_testing.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=684759
+-->
+<head>
+ <title>Test for Bug 684759</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=684759">Mozilla Bug 684759</a>
+<iframe src="preserve3d_sorting_hit_testing_iframe.html" id="iframe" height="1000" width="1000" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 684759 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ var iframe = document.getElementById("iframe");
+
+ var doc = iframe.contentDocument;
+
+ var big= doc.getElementById("big");
+ var small = doc.getElementById("small");
+
+ function check(x, y, expected_element, description)
+ {
+ is(doc.elementFromPoint(x, y).id, expected_element.id,
+ "point (" + x + ", " + y + "): " + description);
+ }
+
+ check(650, 250, small, "Small object should be infront of big");
+ check(650, 308, big, "Check bounds of small object");
+ check(650, 207, big, "Check bounds of small object");
+ check(607, 250, big, "Check bounds of small object");
+ check(708, 250, big, "Check bounds of small object");
+
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_preserve3d_sorting_hit_testing2.html b/layout/base/tests/test_preserve3d_sorting_hit_testing2.html
new file mode 100644
index 0000000000..4199907eee
--- /dev/null
+++ b/layout/base/tests/test_preserve3d_sorting_hit_testing2.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1241394
+-->
+<head>
+ <title>Test for Bug 1241394</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1241394">Mozilla Bug 1241394</a>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1241394 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+ var win;
+
+ window.child_opened = function(doc) {
+ var container= doc.getElementById("container");
+
+ isnot(doc.elementFromPoint(60, 50).id, container.id,
+ "point (50, 50): should not hit background");
+
+ win.close();
+ SimpleTest.finish();
+ }
+
+ win = window.open("preserve3d_sorting_hit_testing2_iframe.html");
+}
+
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_refreshDriver_hasPendingTick.html b/layout/base/tests/test_refreshDriver_hasPendingTick.html
new file mode 100644
index 0000000000..eb92c1fb92
--- /dev/null
+++ b/layout/base/tests/test_refreshDriver_hasPendingTick.html
@@ -0,0 +1,95 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1756269
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test for Bug 1756269: the nsIDOMWindowUtils.refreshDriverHasPendingTick API</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+ <style>
+ @keyframes growWidth {
+ from { width: 20px; }
+ to { width: 30px; }
+ }
+ .animating {
+ animation: 1s growWidth infinite alternate;
+ }
+ #sometimesAnimated {
+ background: blue;
+ width: 10px;
+ height: 10px;
+ }
+ </style>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1756269">Mozilla Bug 1756269</a>
+<div id="display">
+ <div id="sometimesAnimated"></div>
+</div>
+<pre id="test">
+<script>
+"use strict";
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestFlakyTimeout("need to allow time to pass so that we can " +
+ "detect unwanted extra refresh driver ticks");
+const gUtils = SpecialPowers.getDOMWindowUtils(window);
+
+async function startAnimAndExpectTick() {
+ // Start an animation:
+ sometimesAnimated.classList.add("animating");
+
+ // double-rAF to flush pending paints:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+
+ ok(gUtils.refreshDriverHasPendingTick,
+ "Expecting refresh driver to be ticking when animated content is present");
+
+ // clean up, i.e. remove animation:
+ sometimesAnimated.classList.remove("animating");
+
+ // double-rAF to flush pending paints:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+}
+
+async function expectTicksToStop() {
+ let didStopTicking = false;
+ // Note: The maximum loop count here is an arbitrary large value, just to let
+ // us gracefully handle edge cases where multiple setTimeouts resolve before
+ // a pending refresh driver tick. Really, we just want to be sure the refresh
+ // driver *eventually* stops ticking, and we can do so gracefully by polling
+ // with some generous-but-finite number of checks here.
+ for (var i = 0; i < 100; i++) {
+ await new Promise(r => setTimeout(r, 8));
+ if(!gUtils.refreshDriverHasPendingTick) {
+ didStopTicking = true;
+ break;
+ }
+ }
+ ok(didStopTicking, "refresh driver should have eventually stopped ticking");
+}
+
+async function run() {
+ // By default, the refresh driver ticks on its own for some period of time
+ // after pageload. Turn that off so we don't have to wait it out:
+ await SpecialPowers.pushPrefEnv({'set':
+ [['layout.keep_ticking_after_load_ms', 0]]});
+
+ // Start out with a double-rAF, to flush paints from pageload:
+ await new Promise(r => requestAnimationFrame(r));
+ await new Promise(r => requestAnimationFrame(r));
+
+ await startAnimAndExpectTick();
+ await expectTicksToStop();
+ await startAnimAndExpectTick();
+ await expectTicksToStop();
+
+ SimpleTest.finish();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_reftests_with_caret.html b/layout/base/tests/test_reftests_with_caret.html
new file mode 100644
index 0000000000..3935380e5d
--- /dev/null
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -0,0 +1,465 @@
+<!DOCTYPE HTML>
+<title>Reftests with caret drawing</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" href="/tests/SimpleTest/test.css" />
+<style>
+ iframe {
+ border: none;
+ width: 600px;
+ height: 400px;
+ }
+</style>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.requestLongerTimeout(3);
+
+var iframes = [];
+function callbackTestIframe(iframe)
+{
+ iframes.push(iframe);
+
+ if (iframes.length != 2)
+ return;
+
+ var result = iframes[0];
+ var reference = iframes[1];
+ const shouldBeEqual = true;
+ // Using assertSnapshots is important to get the data-URIs of failing tests
+ // dumped into the log in a format that reftest-analyzer.xhtml can process.
+ var passed = assertSnapshots(result.snapshot, reference.snapshot,
+ shouldBeEqual, null /*no fuzz*/, result.src,
+ reference.src);
+
+ // Remove the iframes if the test was successful
+ if (passed) {
+ result.remove();
+ reference.remove();
+ }
+
+ iframes = [];
+ SimpleTest.waitForFocus(nextTest);
+}
+
+function doSnapShot(iframe) {
+ iframe.snapshot = snapshotWindow(iframe.contentWindow, true);
+ callbackTestIframe(iframe);
+};
+
+function remotePageLoaded(callback) {
+ var iframe = this;
+ setTimeout(function(){
+ doSnapShot(iframe);
+ callback();
+ }, 0);
+};
+
+const MAX_ITERATIONS = 1000;
+
+function createIframe(url,next) {
+ var iframe = document.createElement("iframe");
+ iframe.remotePageLoaded = remotePageLoaded;
+ var me = this;
+ var currentIteration = 0;
+ function iframeLoadCompleted() {
+ let loc = iframe.contentWindow.location;
+ if (loc && loc.href == "about:blank") {
+ return;
+ }
+ var docEl = iframe.contentDocument.documentElement;
+ if (docEl.className.includes("reftest-wait")) {
+ if (currentIteration++ > MAX_ITERATIONS) {
+ ok(false, "iframe load for " + url + " timed out");
+ endTest();
+ } else {
+ setTimeout(iframeLoadCompleted, 0);
+ }
+ return;
+ }
+ iframe.remotePageLoaded(function() {
+ if (next) {
+ setTimeout(function(){createIframe(next,null);}, 0)
+ }
+ });
+ }
+ iframe.addEventListener("load", iframeLoadCompleted);
+ window.document.body.appendChild(iframe);
+ iframe.clientHeight; // flush layout.
+ iframe.src = url;
+ iframe.focus();
+};
+
+function refTest(test,ref) {
+ createIframe(test,ref);
+};
+
+var caretBlinkTime = null;
+async function endTest() {
+ await SpecialPowers.spawn(window.parent, [], () => {
+ content.document.styleSheets[content.document.styleSheets.length-1].deleteRule(0);
+ });
+
+ // finish(), yet let the test actually end first, to be safe.
+ SimpleTest.executeSoon(SimpleTest.finish);
+}
+
+var tests = [
+ [ 'bug106855-1.html' , 'bug106855-1-ref.html' ] ,
+ [ 'bug106855-2.html' , 'bug106855-1-ref.html' ] ,
+ [ 'bug389321-2.html' , 'bug389321-2-ref.html' ] ,
+ [ 'bug613807-1.html' , 'bug613807-1-ref.html' ] ,
+ [ 'bug1082486-1.html', 'bug1082486-1-ref.html'] ,
+ [ 'bug1082486-2.html', 'bug1082486-2-ref.html'] ,
+ // The following test cases uses mouse events. We need to make
+ // AccessibleCaret unhide for them.
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.accessiblecaret.hide_carets_for_mouse_input', false]]}, nextTest);} ,
+ [ 'bug1516963-1.html', 'bug1516963-1-ref.html'] ,
+ [ 'bug1516963-2.html', 'bug1516963-2-ref.html'] ,
+ [ 'bug1516963-3.html', 'bug1516963-3-ref.html'] ,
+ [ 'bug1516963-4.html', 'bug1516963-4-ref.html'] ,
+ [ 'bug1516963-5.html', 'bug1516963-5-ref.html'] ,
+ [ 'bug1516963-6.html', 'bug1516963-6-ref.html'] ,
+ [ 'bug1550869-1a.html', 'bug1550869-1-ref.html'] ,
+ [ 'bug1550869-1b.html', 'bug1550869-1-ref.html'] ,
+ [ 'bug1550869-2a.html', 'bug1550869-2-ref.html'] ,
+ [ 'bug1550869-2b.html', 'bug1550869-2-ref.html'] ,
+ [ 'bug1550869-2c.html', 'bug1550869-2-ref.html'] ,
+ [ 'bug1550869-2d.html', 'bug1550869-2-ref.html'] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.hide_carets_for_mouse_input']]}, nextTest);} ,
+ // The following test cases are all involving with one sending
+ // synthesizeKey(), the other without. They fail when accessiblecaret
+ // is enabled. Test them with the preference off.
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.accessiblecaret.enabled_on_touch', false]]}, nextTest);} ,
+ [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] ,
+ [ 'bug240933-2.html' , 'bug240933-1-ref.html' ] ,
+ [ 'bug389321-1.html' , 'bug389321-1-ref.html' ] ,
+ [ 'bug389321-3.html' , 'bug389321-3-ref.html' ] ,
+ [ 'bug482484.html' , 'bug482484-ref.html' ] ,
+ [ 'bug503399.html' , 'bug503399-ref.html' ] ,
+ [ 'bug585922.html' , 'bug585922-ref.html' ] ,
+ [ 'bug597519-1.html' , 'bug597519-1-ref.html' ] ,
+ [ 'bug602141-1.html' , 'bug602141-1-ref.html' ] ,
+ [ 'bug602141-2.html' , 'bug602141-2-ref.html' ] ,
+ [ 'bug602141-3.html' , 'bug602141-3-ref.html' ] ,
+ [ 'bug602141-4.html' , 'bug602141-4-ref.html' ] ,
+ [ 'bug612271-1.html' , 'bug612271-ref.html' ] ,
+ [ 'bug612271-2.html' , 'bug612271-ref.html' ] ,
+ [ 'bug612271-3.html' , 'bug612271-ref.html' ] ,
+ [ 'bug613433-1.html' , 'bug613433-ref.html' ] ,
+ [ 'bug613433-2.html' , 'bug613433-ref.html' ] ,
+ [ 'bug613433-3.html' , 'bug613433-ref.html' ] ,
+ [ 'bug632215-1.html' , 'bug632215-ref.html' ] ,
+ [ 'bug632215-2.html' , 'bug632215-ref.html' ] ,
+ [ 'bug633044-1.html' , 'bug633044-1-ref.html' ] ,
+ [ 'bug634406-1.html' , 'bug634406-1-ref.html' ] ,
+ [ 'bug644428-1.html' , 'bug644428-1-ref.html' ] ,
+ [ 'input-maxlength-valid-before-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-minlength-valid-before-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-maxlength-ui-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-maxlength-ui-invalid-change.html', 'input-invalid-ref.html'] ,
+ [ 'input-minlength-ui-valid-change.html', 'input-valid-ref.html'] ,
+ [ 'input-minlength-ui-invalid-change.html', 'input-invalid-ref.html'] ,
+
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.forms.reveal-password-button.enabled', false]]}, nextTest);} ,
+ [ 'input-password-remask.html', 'input-password-remask-ref.html' ],
+
+ // The following hash means language.
+ function() {SpecialPowers.pushPrefEnv({'set': [['editor.password.mask_delay', 0]]}, nextTest);} ,
+ [ 'input-password-RTL-input.html#arabic', 'input-password-RTL-input-ref.html'],
+ [ 'input-password-RTL-input.html#hebrew', 'input-password-RTL-input-ref.html'],
+ function() {SpecialPowers.pushPrefEnv({'clear': [['editor.password.mask_delay']]}, nextTest);} ,
+
+ // The following hash means "text ('_' is a space)" - unmarsk start - unmask end - selection start - selection end[ - text-transform].
+ [ 'input-password-unmask.html#abc_def-0-7-7-7', 'input-password-unmask-ref.html#abc_def-0-7-7-7'],
+ [ 'input-password-unmask.html#abc_def-0-7-7-7-capitalize', 'input-password-unmask-ref.html#Abc_Def-0-7-7-7'],
+ [ 'input-password-unmask.html#abc_def-0-7-7-7-uppercase', 'input-password-unmask-ref.html#ABC_DEF-0-7-7-7'],
+ [ 'input-password-unmask.html#ABC_DEF-0-7-7-7-lowercase', 'input-password-unmask-ref.html#abc_def-0-7-7-7'],
+ [ 'input-password-unmask.html#abc_def-0-1-1-1', 'input-password-unmask-ref.html#abc_def-0-1-1-1'],
+ [ 'input-password-unmask.html#abc_def-2-4-1-5', 'input-password-unmask-ref.html#abc_def-2-4-1-5'],
+ [ 'input-password-unmask.html#abc_def-6-7-7-7', 'input-password-unmask-ref.html#abc_def-6-7-7-7'],
+
+ // The following hash means unmarsk start - unmask end - selection start - selection end.
+ // The value is "a&#x1f914;b" so that the range is 0-4.
+ [ 'input-password-unmask-around-emoji.html#0-4-4-4', 'input-password-unmask-around-emoji-ref.html#0-4-4-4'],
+ [ 'input-password-unmask-around-emoji.html#0-3-3-3', 'input-password-unmask-around-emoji-ref.html#0-3-3-3'],
+ [ 'input-password-unmask-around-emoji.html#0-1-3-3', 'input-password-unmask-around-emoji-ref.html#0-1-3-3'],
+ [ 'input-password-unmask-around-emoji.html#1-2-3-3', 'input-password-unmask-around-emoji-ref.html#1-2-3-3'],
+ [ 'input-password-unmask-around-emoji.html#1-3-3-3', 'input-password-unmask-around-emoji-ref.html#1-3-3-3'],
+ [ 'input-password-unmask-around-emoji.html#2-3-3-3', 'input-password-unmask-around-emoji-ref.html#2-3-3-3'],
+ [ 'input-password-unmask-around-emoji.html#3-4-4-4', 'input-password-unmask-around-emoji-ref.html#3-4-4-4'],
+ [ 'input-password-unmask-around-emoji.html#1-4-4-4', 'input-password-unmask-around-emoji-ref.html#1-4-4-4'],
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.forms.reveal-password-button.enabled']]}, nextTest);} ,
+
+ // The following tests something in editable elements so that we need to disable zoom to focused editor.
+ function() {SpecialPowers.pushPrefEnv({'set': [['apz.zoom-to-focused-input.enabled', false]]}, nextTest);} ,
+ [ 'bug956530-1.html' , 'bug956530-1-ref.html' ] , // Clicks an <input> element
+ [ 'bug1097242-1.html', 'bug1097242-1-ref.html'] , // Clicks non-editable and non-selectable element in an editing host
+ [ 'bug1423331-1.html' , 'bug1423331-1-ref.html' ] , // Clicks in an editing host
+ [ 'bug1423331-2.html' , 'bug1423331-2-ref.html' ] , // Clicks in an editing host
+ [ 'bug1506547-4.html' , 'bug1506547-4-ref.html' ] , // Clicks in an editing host
+ [ 'bug1506547-5.html' , 'bug1506547-5-ref.html' ] , // Clicks in an editing host
+ [ 'bug1506547-6.html' , 'bug1506547-5-ref.html' ] , // Clicks in an editing host (reference)
+ [ 'bug1518339-1.html' , 'bug1518339-1-ref.html' ] , // Clicks in an editing host
+ [ 'bug1518339-2.html' , 'bug1518339-2-ref.html' ] , // Clicks in an editing host
+ function() {SpecialPowers.pushPrefEnv({'clear': [['apz.zoom-to-focused-input.enabled']]}, nextTest);} ,
+
+ [ 'input-stoppropagation.html', 'input-stoppropagation-ref.html'] ,
+ [ 'textarea-maxlength-valid-before-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-minlength-valid-before-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-maxlength-ui-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-maxlength-ui-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ [ 'textarea-minlength-ui-valid-change.html', 'textarea-valid-ref.html'] ,
+ [ 'textarea-minlength-ui-invalid-change.html', 'textarea-invalid-ref.html'] ,
+ function() {SpecialPowers.pushPrefEnv({'set': [['bidi.browser.ui', true]]}, nextTest);} ,
+ [ 'bug646382-1.html' , 'bug646382-1-ref.html' ] ,
+ [ 'bug646382-2.html' , 'bug646382-2-ref.html' ] ,
+ [ 'bug664087-1.html' , 'bug664087-1-ref.html' ] ,
+ [ 'bug664087-2.html' , 'bug664087-2-ref.html' ] ,
+ [ 'bug682712-1.html' , 'bug682712-1-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['bidi.browser.ui']]}, nextTest);} ,
+ [ 'bug746993-1.html' , 'bug746993-1-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);} ,
+ [ 'bug966992-1.html' , 'bug966992-1-ref.html' ] ,
+ [ 'bug966992-2.html' , 'bug966992-2-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.css.overflow-clip-box.enabled']]}, nextTest);} ,
+ [ 'bug989012-1.html' , 'bug989012-1-ref.html' ] ,
+ [ 'bug989012-2.html' , 'bug989012-2-ref.html' ] ,
+ [ 'bug989012-3.html' , 'bug989012-3-ref.html' ] ,
+ [ 'bug1007065-1.html' , 'bug1007065-1-ref.html' ] ,
+ [ 'bug1007067-1.html' , 'bug1007067-1-ref.html' ] ,
+ [ 'bug1061468.html' , 'bug1061468-ref.html' ] ,
+ [ 'bug1109968-1.html', 'bug1109968-1-ref.html'] ,
+ [ 'bug1109968-2.html', 'bug1109968-2-ref.html'] ,
+ // [ 'bug1123067-1.html' , 'bug1123067-ref.html' ] , TODO: bug 1129205
+ [ 'bug1123067-2.html' , 'bug1123067-ref.html' ] ,
+ [ 'bug1123067-3.html' , 'bug1123067-ref.html' ] ,
+ [ 'bug1132768-1.html' , 'bug1132768-1-ref.html'] ,
+ [ 'bug1237236-1.html' , 'bug1237236-1-ref.html' ] ,
+ [ 'bug1237236-2.html' , 'bug1237236-2-ref.html' ] ,
+ [ 'bug1258308-1.html' , 'bug1258308-1-ref.html' ] ,
+ [ 'bug1258308-2.html' , 'bug1258308-2-ref.html' ] ,
+ [ 'bug1259949-1.html' , 'bug1259949-1-ref.html'] ,
+ [ 'bug1259949-2.html' , 'bug1259949-2-ref.html'] ,
+ [ 'bug1263288.html' , 'bug1263288-ref.html'] ,
+ [ 'bug1263357-1.html' , 'bug1263357-1-ref.html'] ,
+ [ 'bug1263357-2.html' , 'bug1263357-2-ref.html'] ,
+ [ 'bug1263357-3.html' , 'bug1263357-3-ref.html'] ,
+ [ 'bug1263357-4.html' , 'bug1263357-4-ref.html'] ,
+ [ 'bug1263357-5.html' , 'bug1263357-5-ref.html'] ,
+ [ 'bug1354478-1.html' , 'bug1354478-1-ref.html'] ,
+ [ 'bug1354478-2.html' , 'bug1354478-2-ref.html'] ,
+ [ 'bug1354478-3.html' , 'bug1354478-3-ref.html'] ,
+ [ 'bug1354478-4.html' , 'bug1354478-4-ref.html'] ,
+ [ 'bug1354478-5.html' , 'bug1354478-5-ref.html'] ,
+ [ 'bug1354478-6.html' , 'bug1354478-6-ref.html'] ,
+ [ 'bug1359411.html' , 'bug1359411-ref.html' ] ,
+ [ 'bug1415416.html' , 'bug1415416-ref.html' ] ,
+ // FIXME(bug 1434949): These two fail in some platforms.
+ // [ 'bug1423331-3.html' , 'bug1423331-1-ref.html' ] ,
+ // [ 'bug1423331-4.html' , 'bug1423331-2-ref.html' ] ,
+ [ 'bug1484094-1.html' , 'bug1484094-1-ref.html' ] ,
+ [ 'bug1484094-2.html' , 'bug1484094-2-ref.html' ] ,
+ [ 'bug1506547-1.html' , 'bug1506547-2.html' ] ,
+ [ 'bug1506547-2.html' , 'bug1506547-3.html' ] ,
+ [ 'bug1510942-1.html' , 'bug1510942-1-ref.html' ] ,
+ [ 'bug1510942-2.html' , 'bug1510942-2-ref.html' ] ,
+ [ 'bug1524266-1.html' , 'bug1524266-1-ref.html' ] ,
+ // Checks that the caret isn't occluded by children background content.
+ [ 'bug1591282-1.html' , 'bug1591282-1-ref.html' ] ,
+ // Caret on contenteditable with abspos and / or empty content.
+ [ 'bug1634543-1.html' , 'bug1634543-1-ref.html' ] ,
+ [ 'bug1634543-2.html' , 'bug1634543-1-ref.html' ] ,
+ // TODO(emilio): This fails because nsInlineFrame::GetCaretBaseline doesn't
+ // return one line-height for an empty inline, and it probably should..
+ // [ 'bug1634543-3.html' , 'bug1634543-1-ref.html' ] ,
+ [ 'bug1634543-4.html' , 'bug1634543-1-ref.html' ] ,
+ // Caret + line-height + pseudo-element only.
+ [ 'bug1634743-1.html' , 'bug1634743-1-ref.html' ] ,
+ [ 'bug1637476-1.html' , 'bug1637476-1-ref.html' ] ,
+ [ 'bug1637476-2.html' , 'bug1637476-2-ref.html' ] ,
+ [ 'bug1637476-3.html' , 'bug1637476-3-ref.html' ] ,
+ [ 'bug1663475-1.html' , 'bug1663475-1-ref.html' ] ,
+ [ 'bug1663475-2.html' , 'bug1663475-2-ref.html' ] ,
+ // shift+arrow key should select non-editable only
+ [ 'bug1670531-1.html' , 'bug1670531-2.html' ] ,
+ [ 'bug1670531-3.html' , 'bug1670531-3-ref.html' ] ,
+ [ 'bug1670531-4.html' , 'bug1670531-3-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled_on_touch']]}, nextTest);} ,
+ function() {SpecialPowers.pushPrefEnv({'set': [['accessibility.browsewithcaret', true]]}, nextTest);} ,
+ [ 'bug1529492-1.html' , 'bug1529492-1-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['accessibility.browsewithcaret']]}, nextTest);} ,
+ [ 'interlinePosition-after-Selection-addRange.html', 'interlinePosition-after-Selection-addRange-ref.html' ] ,
+ [ 'collapse-selection-into-editing-host-during-blur-of-input.html', 'collapse-selection-into-editing-host-during-blur-of-input-ref.html' ] ,
+];
+
+if (!navigator.appVersion.includes("Android")) {
+ tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]); // Tests spellchecker
+ tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]); // Tests spellchecker
+ tests.push([ 'bug923376.html' , 'bug923376-ref.html' ]); // Tests spellchecker
+ tests.push([ 'bug1496118.html' , 'bug1496118-ref.html' ]); // Tests DnD
+} else {
+ is(SpecialPowers.getIntPref("layout.spellcheckDefault"), 0, "Spellcheck should be turned off for this platform or this if..else check removed");
+}
+
+if (navigator.platform.includes("Linux") && !navigator.appVersion.includes("Android")) {
+ tests = tests.concat([
+ // Turn off accessiblecaret to prevent it from interfering with the
+ // multi-range selection.
+ function() {SpecialPowers.pushPrefEnv({'set': [['layout.accessiblecaret.enabled_on_touch', false]]}, nextTest);} ,
+ // eDirPrevious, Shift+click
+ [ 'multi-range-user-select.html#prev1S_' , 'multi-range-user-select-ref.html#prev1S_' ] ,
+ [ 'multi-range-user-select.html#prev2S_' , 'multi-range-user-select-ref.html#prev2S_' ] ,
+ [ 'multi-range-user-select.html#prev3S_' , 'multi-range-user-select-ref.html#prev3S_' ] ,
+ [ 'multi-range-user-select.html#prev4S_' , 'multi-range-user-select-ref.html#prev4S_' ] ,
+ [ 'multi-range-user-select.html#prev5S_' , 'multi-range-user-select-ref.html#prev5S_' ] ,
+ [ 'multi-range-user-select.html#prev6S_' , 'multi-range-user-select-ref.html#prev6S_' ] ,
+ [ 'multi-range-user-select.html#prev7S_' , 'multi-range-user-select-ref.html#prev7S_' ] ,
+ // eDirPrevious, Shift+Accel+click
+ [ 'multi-range-user-select.html#prev1SA' , 'multi-range-user-select-ref.html#prev1SA' ] ,
+ [ 'multi-range-user-select.html#prev2SA' , 'multi-range-user-select-ref.html#prev2SA' ] ,
+ [ 'multi-range-user-select.html#prev3SA' , 'multi-range-user-select-ref.html#prev3SA' ] ,
+ [ 'multi-range-user-select.html#prev4SA' , 'multi-range-user-select-ref.html#prev4SA' ] ,
+ [ 'multi-range-user-select.html#prev5SA' , 'multi-range-user-select-ref.html#prev5SA' ] ,
+ [ 'multi-range-user-select.html#prev6SA' , 'multi-range-user-select-ref.html#prev6SA' ] ,
+ [ 'multi-range-user-select.html#prev7SA' , 'multi-range-user-select-ref.html#prev7SA' ] ,
+ // eDirPrevious, Accel+drag-select (adding an additional range)
+ [ 'multi-range-user-select.html#prev1AD' , 'multi-range-user-select-ref.html#prev1AD' ] ,
+ [ 'multi-range-user-select.html#prev7AD' , 'multi-range-user-select-ref.html#prev7AD' ] ,
+ // eDirPrevious, Accel+drag-select (bug 1128722)
+ [ 'multi-range-user-select.html#prev8AD' , 'multi-range-user-select-ref.html#prev8AD' ] ,
+ // eDirPrevious, VK_RIGHT / LEFT
+ [ 'multi-range-user-select.html#prev1SR' , 'multi-range-user-select-ref.html#prev1SR' ] ,
+ [ 'multi-range-user-select.html#prev1SL' , 'multi-range-user-select-ref.html#prev1SL' ] ,
+ // eDirNext, Shift+click
+ [ 'multi-range-user-select.html#next1S_' , 'multi-range-user-select-ref.html#next1S_' ] ,
+ [ 'multi-range-user-select.html#next2S_' , 'multi-range-user-select-ref.html#next2S_' ] ,
+ [ 'multi-range-user-select.html#next3S_' , 'multi-range-user-select-ref.html#next3S_' ] ,
+ [ 'multi-range-user-select.html#next4S_' , 'multi-range-user-select-ref.html#next4S_' ] ,
+ [ 'multi-range-user-select.html#next5S_' , 'multi-range-user-select-ref.html#next5S_' ] ,
+ [ 'multi-range-user-select.html#next6S_' , 'multi-range-user-select-ref.html#next6S_' ] ,
+ [ 'multi-range-user-select.html#next7S_' , 'multi-range-user-select-ref.html#next7S_' ] ,
+ // eDirNext, Shift+Accel+click
+ [ 'multi-range-user-select.html#next1SA' , 'multi-range-user-select-ref.html#next1SA' ] ,
+ [ 'multi-range-user-select.html#next2SA' , 'multi-range-user-select-ref.html#next2SA' ] ,
+ [ 'multi-range-user-select.html#next3SA' , 'multi-range-user-select-ref.html#next3SA' ] ,
+ [ 'multi-range-user-select.html#next4SA' , 'multi-range-user-select-ref.html#next4SA' ] ,
+ [ 'multi-range-user-select.html#next5SA' , 'multi-range-user-select-ref.html#next5SA' ] ,
+ [ 'multi-range-user-select.html#next6SA' , 'multi-range-user-select-ref.html#next6SA' ] ,
+ [ 'multi-range-user-select.html#next7SA' , 'multi-range-user-select-ref.html#next7SA' ] ,
+ // eDirNext, Accel+drag-select (adding an additional range)
+ [ 'multi-range-user-select.html#next1AD' , 'multi-range-user-select-ref.html#next1AD' ] ,
+ [ 'multi-range-user-select.html#next7AD' , 'multi-range-user-select-ref.html#next7AD' ] ,
+ // eDirNext, Accel+drag-select (bug 1128722)
+ [ 'multi-range-user-select.html#next8AD' , 'multi-range-user-select-ref.html#next8AD' ] ,
+ // eDirNext, VK_RIGHT / LEFT
+ [ 'multi-range-user-select.html#next1SR' , 'multi-range-user-select-ref.html#next1SR' ] ,
+ [ 'multi-range-user-select.html#next1SL' , 'multi-range-user-select-ref.html#next1SL' ] ,
+ // eDirPrevious, Shift+click
+ [ 'multi-range-script-select.html#prev1S_' , 'multi-range-script-select-ref.html#prev1S_' ] ,
+ [ 'multi-range-script-select.html#prev2S_' , 'multi-range-script-select-ref.html#prev2S_' ] ,
+ [ 'multi-range-script-select.html#prev3S_' , 'multi-range-script-select-ref.html#prev3S_' ] ,
+ [ 'multi-range-script-select.html#prev4S_' , 'multi-range-script-select-ref.html#prev4S_' ] ,
+ [ 'multi-range-script-select.html#prev5S_' , 'multi-range-script-select-ref.html#prev5S_' ] ,
+ [ 'multi-range-script-select.html#prev6S_' , 'multi-range-script-select-ref.html#prev6S_' ] ,
+ [ 'multi-range-script-select.html#prev7S_' , 'multi-range-script-select-ref.html#prev7S_' ] ,
+ // eDirPrevious, Shift+Accel+click
+ [ 'multi-range-script-select.html#prev1SA' , 'multi-range-script-select-ref.html#prev1SA' ] ,
+ [ 'multi-range-script-select.html#prev2SA' , 'multi-range-script-select-ref.html#prev2SA' ] ,
+ [ 'multi-range-script-select.html#prev3SA' , 'multi-range-script-select-ref.html#prev3SA' ] ,
+ [ 'multi-range-script-select.html#prev4SA' , 'multi-range-script-select-ref.html#prev4SA' ] ,
+ [ 'multi-range-script-select.html#prev5SA' , 'multi-range-script-select-ref.html#prev5SA' ] ,
+ [ 'multi-range-script-select.html#prev6SA' , 'multi-range-script-select-ref.html#prev6SA' ] ,
+ [ 'multi-range-script-select.html#prev7SA' , 'multi-range-script-select-ref.html#prev7SA' ] ,
+ // eDirPrevious, Accel+drag-select (adding an additional range)
+ [ 'multi-range-script-select.html#prev1AD' , 'multi-range-script-select-ref.html#prev1AD' ] ,
+ [ 'multi-range-script-select.html#prev7AD' , 'multi-range-script-select-ref.html#prev7AD' ] ,
+ // eDirPrevious, VK_RIGHT / LEFT
+ [ 'multi-range-script-select.html#prev1SR' , 'multi-range-script-select-ref.html#prev1SR' ] ,
+ [ 'multi-range-script-select.html#prev1SL' , 'multi-range-script-select-ref.html#prev1SL' ] ,
+ // eDirNext, Shift+click
+ [ 'multi-range-script-select.html#next1S_' , 'multi-range-script-select-ref.html#next1S_' ] ,
+ [ 'multi-range-script-select.html#next2S_' , 'multi-range-script-select-ref.html#next2S_' ] ,
+ [ 'multi-range-script-select.html#next3S_' , 'multi-range-script-select-ref.html#next3S_' ] ,
+ [ 'multi-range-script-select.html#next4S_' , 'multi-range-script-select-ref.html#next4S_' ] ,
+ [ 'multi-range-script-select.html#next5S_' , 'multi-range-script-select-ref.html#next5S_' ] ,
+ [ 'multi-range-script-select.html#next6S_' , 'multi-range-script-select-ref.html#next6S_' ] ,
+ [ 'multi-range-script-select.html#next7S_' , 'multi-range-script-select-ref.html#next7S_' ] ,
+ // eDirNext, Shift+Accel+click
+ [ 'multi-range-script-select.html#next1SA' , 'multi-range-script-select-ref.html#next1SA' ] ,
+ [ 'multi-range-script-select.html#next2SA' , 'multi-range-script-select-ref.html#next2SA' ] ,
+ [ 'multi-range-script-select.html#next3SA' , 'multi-range-script-select-ref.html#next3SA' ] ,
+ [ 'multi-range-script-select.html#next4SA' , 'multi-range-script-select-ref.html#next4SA' ] ,
+ [ 'multi-range-script-select.html#next5SA' , 'multi-range-script-select-ref.html#next5SA' ] ,
+ [ 'multi-range-script-select.html#next6SA' , 'multi-range-script-select-ref.html#next6SA' ] ,
+ [ 'multi-range-script-select.html#next7SA' , 'multi-range-script-select-ref.html#next7SA' ] ,
+ // eDirNext, Accel+drag-select (adding an additional range)
+ [ 'multi-range-script-select.html#next1AD' , 'multi-range-script-select-ref.html#next1AD' ] ,
+ [ 'multi-range-script-select.html#next7AD' , 'multi-range-script-select-ref.html#next7AD' ] ,
+ // eDirNext, VK_RIGHT / LEFT
+ [ 'multi-range-script-select.html#next1SR' , 'multi-range-script-select-ref.html#next1SR' ] ,
+ [ 'multi-range-script-select.html#next1SL' , 'multi-range-script-select-ref.html#next1SL' ] ,
+
+ // Tries to select and delete non-selectable content in a user-select subtree.
+ [ 'bug1524266-2.html' , 'bug1524266-2-ref.html' ] ,
+ [ 'bug1524266-3.html' , 'bug1524266-2-ref.html' ] ,
+ // Tries to select and delete non-editable content in a user-select subtree.
+ [ 'bug1524266-4.html' , 'bug1524266-2-ref.html' ] ,
+ // Tries to edit an <input type="number"> with arrows inside a user-select: none element.
+ [ 'bug1611661.html' , 'bug1611661-ref.html' ] ,
+ function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled_on_touch']]}, nextTest);} ,
+ ]);
+}
+
+var testIndex = 0;
+
+// Change it to something like /bug1524266/ to skip all other tests and make
+// debugging easier...
+const DEBUG_TEST_FILTER = null;
+
+function nextTest() {
+ if (testIndex < tests.length) {
+ let test = tests[testIndex];
+ if (typeof(test) == 'function') {
+ test();
+ } else if (!DEBUG_TEST_FILTER || DEBUG_TEST_FILTER.test(test[0])) {
+ refTest(test[0], test[1]);
+ } else {
+ setTimeout(nextTest, 0);
+ }
+ ++testIndex;
+ } else {
+ endTest();
+ }
+}
+async function runTests() {
+ try {
+ if (window.parent) {
+ await SpecialPowers.spawn(window.parent, [], () => {
+ content.document.styleSheets[content.document.styleSheets.length-1]
+ .insertRule("iframe#testframe{width:600px;height:400px}",0);
+ });
+ }
+ try {
+ caretBlinkTime = SpecialPowers.getIntPref("ui.caretBlinkTime");
+ } catch (e) {}
+ SpecialPowers.pushPrefEnv({'set': [['ui.caretBlinkTime', -1]]}, nextTest);
+ } catch(e) {
+ endTest();
+ }
+}
+
+SimpleTest.waitForFocus(runTests);
+
+</script>
+<body>
+</body>
diff --git a/layout/base/tests/test_resize_flush.html b/layout/base/tests/test_resize_flush.html
new file mode 100644
index 0000000000..ad14a8f700
--- /dev/null
+++ b/layout/base/tests/test_resize_flush.html
@@ -0,0 +1,51 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1279202
+-->
+<head>
+ <title>Test for Bug 1279202</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body onload="run()">
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1279202">Mozilla Bug 1279202</a>
+<iframe src="resize_flush_iframe.html" id="iframe" height="200" width="200" style="border:none"></iframe>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1279202 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function run() {
+
+ var iframe = document.getElementById("iframe");
+ var doc = iframe.contentDocument.documentElement;
+ var win = iframe.contentWindow;
+ var body = iframe.contentDocument.body;
+
+ // Flush any pending layout changes before we start.
+ var width = doc.clientWidth;
+
+ // Resize the iframe
+ iframe.width = '300px';
+
+ // Flush pending style changes, but not layout ones. We do this twice because the first flush
+ // does a partial flush of the resize (setting the size on the pres context) which sets the
+ // need style flush flag again. The second call makes sure mNeedStyleFlush is false.
+ var color = win.getComputedStyle(body).getPropertyValue("background-color");
+ color = win.getComputedStyle(body).getPropertyValue("background-color");
+ is(color, "rgb(0, 128, 0)", "Style flush not completed when resizing an iframe!");
+
+ // Query the size of the inner document and make sure it has had a layout flush.
+ width = doc.clientWidth;
+
+ is(width, 300, "Layout flush not completed when resizing an iframe!");
+ SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_scroll_event_ordering.html b/layout/base/tests/test_scroll_event_ordering.html
new file mode 100644
index 0000000000..9626d6bb6b
--- /dev/null
+++ b/layout/base/tests/test_scroll_event_ordering.html
@@ -0,0 +1,63 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=785588
+-->
+<head>
+ <title>Test for Bug 785588 --- ordering of scroll-related events</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=785588">Mozilla Bug 785588</a>
+<div id="content">
+ <div id="d" style="border:2px solid black; width:100px; height:100px; overflow:auto">
+ <div id="inner" style="height:200px;">Hello</div>
+ </div>
+</div>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+
+var smoothScrollPref = "general.smoothScroll";
+
+var d = document.getElementById("d");
+d.scrollTop = 0;
+var inner = document.getElementById("inner");
+
+var state = "initial";
+
+function onFrame() {
+ is(state, "didOnScroll", "Must have got scroll event already");
+ state = "didOnFrame";
+ SimpleTest.finish();
+}
+
+function onScroll() {
+ is(state, "initial", "Must be in initial state");
+ ok(d.scrollTop > 0, "Must have scrolled by some amount (got " + d.scrollTop + ")");
+ state = "didOnScroll";
+}
+
+function doTest() {
+ window.getSelection().collapse(inner.firstChild, 0);
+ window.requestAnimationFrame(onFrame);
+ d.onscroll = onScroll;
+ d.scroll(0, 100);
+}
+
+function prepareTest() {
+ // Start the test after we've gotten at least one rAF callback, to make sure
+ // that rAF is no longer throttled. (See bug 1145439.)
+ window.requestAnimationFrame(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, doTest);
+ });
+}
+
+SimpleTest.waitForFocus(prepareTest);
+
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_scroll_into_view_in_oopif.html b/layout/base/tests/test_scroll_into_view_in_oopif.html
new file mode 100644
index 0000000000..7264a20d62
--- /dev/null
+++ b/layout/base/tests/test_scroll_into_view_in_oopif.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<style>
+#scroller {
+ width: 300px;
+ height: 300px;
+ overflow: scroll;
+}
+#spacer {
+ width: 100%;
+ height: 1000px;
+}
+</style>
+<div id="scroller">
+ <div id="spacer"></div>
+ <iframe id="iframe"></iframe>
+</div>
diff --git a/layout/base/tests/test_scroll_selection_into_view.html b/layout/base/tests/test_scroll_selection_into_view.html
new file mode 100644
index 0000000000..cbd9db015b
--- /dev/null
+++ b/layout/base/tests/test_scroll_selection_into_view.html
@@ -0,0 +1,97 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for scrolling selection into view</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var ANCHOR = 0;
+var FOCUS = 1;
+var win;
+
+function testCollapsed(id, vPercent, startAt, expected) {
+ var selection = SpecialPowers.wrap(win.getSelection());
+
+ var c = win.document.getElementById("c" + id);
+ var target = win.document.getElementById("target" + id);
+ if (target.contentDocument) {
+ selection = SpecialPowers.wrap(target.contentWindow.getSelection());
+ target = target.contentDocument.getElementById("target" + id);
+ }
+ selection.collapse(target.parentNode, 0);
+ c.scrollTop = startAt;
+ selection.scrollIntoView(FOCUS, true, vPercent, 0);
+ is(c.scrollTop, expected, "Scrolling " + target.id +
+ " into view with vPercent " + vPercent + ", starting at " + startAt);
+}
+
+function doTest() {
+ // Test scrolling an element smaller than the scrollport
+ testCollapsed("1", 0, 0, 400);
+ testCollapsed("1", 100, 0, 220);
+ testCollapsed("1", -1, 0, 220);
+ testCollapsed("1", 0, 500, 400);
+ testCollapsed("1", 100, 500, 220);
+ testCollapsed("1", -1, 500, 400);
+
+ // overflow:hidden elements should not be scrolled by selection
+ // scrolling-into-view
+ testCollapsed("2", 0, 0, 0);
+ testCollapsed("2", 100, 0, 0);
+ testCollapsed("2", -1, 0, 0);
+ testCollapsed("2", 0, 500, 500);
+ testCollapsed("2", 100, 500, 500);
+ testCollapsed("2", -1, 500, 500);
+
+ // Test scrolling an element larger than the scrollport
+ testCollapsed("3", 0, 0, 400);
+ testCollapsed("3", 100, 0, 500);
+ testCollapsed("3", -1, 0, 400);
+ testCollapsed("3", 0, 1000, 400);
+ testCollapsed("3", 100, 1000, 500);
+ // If the element can't be completely visible, show as much as possible,
+ // and don't hide anything which was initially visible.
+ testCollapsed("3", -1, 1000, 500);
+
+ // Test scrolling an element larger than the scrollport
+ testCollapsed("4", 0, 0, 400);
+ testCollapsed("4", 100, 0, 500);
+ testCollapsed("4", -1, 0, 400);
+ testCollapsed("4", 0, 1000, 400);
+ testCollapsed("4", 100, 1000, 500);
+ // If the element can't be completely visible, show as much as possible,
+ // and don't hide anything which was initially visible.
+ testCollapsed("4", -1, 1000, 500);
+
+ // Test that scrolling a translated element into view takes
+ // account of the transform.
+ testCollapsed("5", 0, 0, 400);
+
+ // Test that scrolling a scaled element into view takes
+ // account of the transform.
+ testCollapsed("6", 0, 0, 150);
+
+ // Test that scrolling an element with a translated, scrolling container
+ // into view takes account of the transform.
+ testCollapsed("7", 0, 0, 400);
+
+ win.close();
+ SimpleTest.finish();
+}
+
+function openWindow() {
+ win = open("scroll_selection_into_view_window.html", "_blank", "width=500,height=350");
+}
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(openWindow);
+</script>
+</pre>
+</body>
+
+</html>
diff --git a/layout/base/tests/test_scroll_space_no_range_overflow_scroll.html b/layout/base/tests/test_scroll_space_no_range_overflow_scroll.html
new file mode 100644
index 0000000000..deed8f4ced
--- /dev/null
+++ b/layout/base/tests/test_scroll_space_no_range_overflow_scroll.html
@@ -0,0 +1,67 @@
+<!doctype html>
+<title>Test for bug 1567237</title>
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.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>
+<style>
+ .spacer { height: 200vh; }
+ .scroller { height: 300px; overflow: scroll; }
+</style>
+<div id="unscrollable" class="scroller" tabindex=0></div>
+<div id="scrollable" class="scroller" tabindex=0>
+ <div class="spacer"></div>
+</div>
+<div class="spacer"></div>
+<script>
+function waitForScrollEvent(target) {
+ return new Promise(resolve => {
+ target.addEventListener("scroll", resolve, { once: true });
+ });
+}
+
+let selectionController =
+ SpecialPowers.wrap(window)
+ .docShell
+ .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+ .getInterface(SpecialPowers.Ci.nsISelectionDisplay)
+ .QueryInterface(SpecialPowers.Ci.nsISelectionController);
+
+function doPageDown(targetExpectedToScroll) {
+ let promise = waitForScrollEvent(targetExpectedToScroll);
+ selectionController.pageMove(true, false);
+ return promise;
+}
+
+promise_test(async function() {
+ await SpecialPowers.pushPrefEnv({"set": [["general.smoothScroll", false]]});
+
+ const rootScroller = document.documentElement;
+ const scrollable = document.querySelector("#scrollable");
+ const unscrollable = document.querySelector("#unscrollable");
+
+ assert_equals(rootScroller.scrollTop, 0, "Root should start unscrolled");
+ assert_equals(scrollable.scrollTop, 0, "#scrollable should start unscrolled");
+ assert_equals(unscrollable.scrollTop, 0, "#unscrollable should not be able to scroll at all");
+
+ assert_true(rootScroller.scrollTopMax > 0, "Should be able to scroll the document element");
+ assert_true(scrollable.scrollTopMax > 0, "Should be able to scroll #scrollable");
+ assert_equals(unscrollable.scrollTopMax, 0, "#unscrollable should not be able to scroll at all (checking scrollTopMax)");
+
+ scrollable.focus();
+ await waitToClearOutAnyPotentialScrolls(window);
+ await doPageDown(scrollable);
+ assert_not_equals(scrollable.scrollTop, 0, "Should have scrolled when pressing space");
+
+ unscrollable.focus();
+ await waitToClearOutAnyPotentialScrolls(window);
+ let rootScrollTop = rootScroller.scrollTop; // Could've scrolled to scroll `scrollable` into view before.
+ await doPageDown(window);
+ assert_equals(unscrollable.scrollTop, 0, "Should not be able to scroll the unscrollable div");
+ assert_not_equals(rootScroller.scrollTop, rootScrollTop, "Root should be able to scroll");
+
+ // Null out the controller. Otherwise we leak the whole window because
+ // PresShell is not cycle-collectable. See bug 1567237.
+ selectionController = null;
+}, "Overflow scroll without range doesn't block scrolling of the main document");
+</script>
diff --git a/layout/base/tests/test_synthmousemove.html b/layout/base/tests/test_synthmousemove.html
new file mode 100644
index 0000000000..fda86c341e
--- /dev/null
+++ b/layout/base/tests/test_synthmousemove.html
@@ -0,0 +1,31 @@
+<!doctype html>
+<title>test synth mouse moves go to the right place with fission</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script>
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+ window.open("file_synthmousemove.html", "_blank");
+}
+
+function reportSuccess() {
+ ok(true, "reportSuccess");
+ SimpleTest.finish();
+}
+
+var smoothScrollPref = "general.smoothScroll";
+function prepareTest() {
+ if (!SpecialPowers.getBoolPref("layout.reflow.synthMouseMove")) {
+ ok(true, "layout.reflow.synthMouseMove is false, we can't run this test");
+ SimpleTest.finish();
+ return;
+ }
+ window.requestAnimationFrame(function() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTest);
+ });
+}
+
+SimpleTest.waitForFocus(prepareTest);
+</script>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints.html b/layout/base/tests/test_transformed_scrolling_repaints.html
new file mode 100644
index 0000000000..1a9162158e
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</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 onload="setPrefAndStartTest()">
+<div id="t" style="transform: scale(1.2, 1.2); transform-origin:top left; width:200px; height:500px; background:yellow; overflow:auto">
+ <div style="height:40px;">Hello</div>
+ <div id="e" style="height:30px; background:lime">Kitty</div>
+ <div style="height:800px; background:yellow">Kitty</div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function startTest() {
+ // Do a couple of scrolls to ensure we've triggered activity heuristics.
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 10;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ t.scrollTop = 15;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+function setPrefAndStartTest() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ // Need a timeout here to allow paint unsuppression before we start the test
+ function() {
+ setTimeout(startTest, 0);
+ }
+ );
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints_2.html b/layout/base/tests/test_transformed_scrolling_repaints_2.html
new file mode 100644
index 0000000000..34a739675f
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints_2.html
@@ -0,0 +1,54 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them (1.1 scale)</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 onload="setPrefAndStartTest()">
+<div id="t" style="transform: scale(1.1, 1.1); transform-origin:top left; width:200px; height:100px; background:yellow; overflow:hidden">
+ <div style="height:40px;"></div>
+ <div id="e" style="height:30px; background:lime"></div>
+ <div style="height:300px; background:yellow"></div>
+</div>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+var t = document.getElementById("t");
+var e = document.getElementById("e");
+var utils = SpecialPowers.getDOMWindowUtils(window);
+
+function startTest() {
+ // Do a couple of scrolls to ensure we've triggered activity heuristics
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 5;
+ waitForAllPaintsFlushed(function () {
+ t.scrollTop = 10;
+ waitForAllPaintsFlushed(function () {
+ // Clear paint state now and scroll again.
+ utils.checkAndClearPaintedState(e);
+ t.scrollTop = 20;
+ waitForAllPaintsFlushed(function () {
+ var painted = utils.checkAndClearPaintedState(e);
+ is(painted, false, "Fully-visible scrolled element should not have been painted");
+ SimpleTest.finish();
+ });
+ });
+ });
+ });
+}
+function setPrefAndStartTest() {
+ SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ // Need a timeout here to allow paint unsuppression before we start the test
+ function() {
+ setTimeout(startTest, 0);
+ }
+ );
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_transformed_scrolling_repaints_3.html b/layout/base/tests/test_transformed_scrolling_repaints_3.html
new file mode 100644
index 0000000000..eb9ad9ba93
--- /dev/null
+++ b/layout/base/tests/test_transformed_scrolling_repaints_3.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body>
+<pre id="test">
+<script type="application/javascript">
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+ {"set": [["layers.single-tile.enabled", false]]},
+ function() {
+ window.open("transformed_scrolling_repaints_3_window.html", "transformed_scrolling_repaints_3",
+ "chrome,width=350,height=450");
+ }
+);
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/test_visual_viewport_in_oopif.html b/layout/base/tests/test_visual_viewport_in_oopif.html
new file mode 100644
index 0000000000..9056336471
--- /dev/null
+++ b/layout/base/tests/test_visual_viewport_in_oopif.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
+<style>
+#iframe {
+ width: 300px;
+ height: 300px;
+ overflow: scroll;
+}
+</style>
+<iframe id="iframe"></iframe>
diff --git a/layout/base/tests/test_zoom_restore_bfcache.html b/layout/base/tests/test_zoom_restore_bfcache.html
new file mode 100644
index 0000000000..d1133a9b04
--- /dev/null
+++ b/layout/base/tests/test_zoom_restore_bfcache.html
@@ -0,0 +1,127 @@
+<!doctype html>
+<meta charset="utf-8">
+<title>Test for zoom restoration when coming from the bfcache</title>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<script src="/tests/SimpleTest/paint_listener.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css"/>
+<script>
+SimpleTest.waitForExplicitFinish();
+
+/**
+ * - main page (this one) opens file_zoom_restore_bfcache.html
+ * - file_zoom_restore_bfcache.html sends "handlePageShow" to main page
+ * - main page sends file_zoom_restore_bfcache.html "case1sendData"
+ * - file_zoom_restore_bfcache.html sends "case1data" to main page
+ * - main page sends "case1click" to file_zoom_restore_bfcache.html
+ * - file_zoom_restore_bfcache.html clicks on <a> element, navigating to uri
+ * file_zoom_restore_bfcache.html?2, and gets bfcached
+ * - file_zoom_restore_bfcache.html?2 sends "handlePageShow" to main page
+ * - main page sends "case2sendData" to file_zoom_restore_bfcache.html?2
+ * - file_zoom_restore_bfcache.html?2 sends "case2data" to main page
+ * - main page sends "case2action" to file_zoom_restore_bfcache.html?2
+ * - file_zoom_restore_bfcache.html?2 sends "case2dataAnimationFrame" to main page
+ * - main page sends "case2back" to file_zoom_restore_bfcache.html?2
+ * - file_zoom_restore_bfcache.html?2 navigates back to file_zoom_restore_bfcache.html
+ * - file_zoom_restore_bfcache.html sends "handlePageShow" to main page
+ * - main page sends "case3sendData to file_zoom_restore_bfcache.html
+ * - file_zoom_restore_bfcache.html sends "case3data" to main page
+ * - main page sends "close to file_zoom_restore_bfcache.html
+ * - file_zoom_restore_bfcache.html closes bc and window and sends back "closed"
+ **/
+
+const originalDPR = window.devicePixelRatio;
+let loadCount = 0;
+var bc = new BroadcastChannel("zoomRestoreBfcache");
+var bcPage2 = new BroadcastChannel("zoomRestoreBfcache?2");
+bc.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ info(`Main page, received command from normal bc=${command}`);
+ switch (command) {
+ case "handlePageShow": {
+ handlePageShow(msgEvent.data.eventPersisted);
+ break;
+ }
+ case "case1data": {
+ is(loadCount, 1, "Case 1");
+ is(msg.devicePixelRatio, originalDPR, "No zoom");
+ bc.postMessage({command: "case1click"});
+ // The end of case 1
+ break;
+ }
+ case "case3data": {
+ is(loadCount, 2, "Case 3");
+ is(msg.devicePixelRatio, originalDPR * 2, "Should preserve zoom when restored");
+ let is_if_ship = SpecialPowers.Services.appinfo.sessionHistoryInParent ? is : todo_is;
+ is_if_ship(msg.frameDevicePixelRatio, originalDPR * 2, "Should preserve zoom on frames too");
+ bc.postMessage({command: "close"});
+ // Now we wait for "closed"
+ break;
+ }
+ case "closed": {
+ is(loadCount, 2, "Case 3");
+ bc.close();
+ SimpleTest.finish();
+ break;
+ }
+ default:
+ ok(false, "should not receive extra messages via BroadcastChannel");
+ }
+}
+bcPage2.onmessage = (msgEvent) => {
+ var msg = msgEvent.data;
+ var command = msg.command;
+ info(`Main page, received command from bc?2=${command}`);
+ switch (command) {
+ case "handlePageShow": {
+ handlePageShow(msgEvent.data.eventPersisted);
+ break;
+ }
+ case "case2data": {
+ is(loadCount, 2, "Case 2");
+ is(msg.devicePixelRatio, originalDPR, "No zoom (yet)")
+ is(msg.frameDevicePixelRatio, originalDPR, "No zoom on frame either");
+ bcPage2.postMessage({command: "case2action"});
+ // Now we wait for "case2dataAnimationFrame"
+ break;
+ }
+ case "case2dataAnimationFrame": {
+ is(loadCount, 2, "Case 2");
+ is(msg.devicePixelRatio, originalDPR * 2, "Zoomed");
+ is(msg.frameDevicePixelRatio, originalDPR * 2, "Zoomed iframe too");
+ bcPage2.postMessage({command: "case2back"});
+ bcPage2.close();
+ // The end of case 2
+ break;
+ }
+ default:
+ ok(false, "should not receive extra messages via BroadcastChannel");
+ }
+}
+function handlePageShow(persisted) {
+ ok(typeof persisted == "boolean", "Should get the persisted state from the pageshow event");
+ if (loadCount == 2) {
+ ok(persisted, "Should've gone into the bfcache after the back navigation");
+ } else {
+ ok(!persisted, "Should NOT be retrieved from bfcache");
+ }
+
+ if (loadCount == 0) {
+ loadCount++;
+ bc.postMessage({command: "case1sendData"});
+ // Now we wait for the "case1data" message
+ } else if (loadCount == 1) {
+ loadCount++;
+ bcPage2.postMessage({command: "case2sendData"});
+ // Now we wait for the "case2data" message
+ } else {
+ bc.postMessage({command: "case3sendData"});
+ // Now we wait for the "case3data" message
+ }
+}
+
+// We want to test Gecko's zoom handling, not the front-end's.
+SpecialPowers.pushPrefEnv({set: [["browser.zoom.siteSpecific", false]]}, () => {
+ window.open('file_zoom_restore_bfcache.html', '_blank', 'noopener');
+});
+</script>
diff --git a/layout/base/tests/textarea-invalid-ref.html b/layout/base/tests/textarea-invalid-ref.html
new file mode 100644
index 0000000000..c5607603d7
--- /dev/null
+++ b/layout/base/tests/textarea-invalid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea style="background-color:red">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-maxlength-invalid-change.html b/layout/base/tests/textarea-maxlength-invalid-change.html
new file mode 100644
index 0000000000..29571678b0
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey("KEY_Backspace");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="2">fooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-ui-invalid-change.html b/layout/base/tests/textarea-maxlength-ui-invalid-change.html
new file mode 100644
index 0000000000..9c674080ac
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is -moz-ui-invalid if the user edits and it's too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey("KEY_Backspace");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="2">fooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-ui-valid-change.html b/layout/base/tests/textarea-maxlength-ui-valid-change.html
new file mode 100644
index 0000000000..2f3d6bda1b
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is -moz-ui-valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey("KEY_Backspace"); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey("KEY_Backspace");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="3">foooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-maxlength-valid-before-change.html b/layout/base/tests/textarea-maxlength-valid-before-change.html
new file mode 100644
index 0000000000..3466d310a4
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is valid until the user edits it, even if it's too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <textarea id="textarea" maxlength="2">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-maxlength-valid-change.html b/layout/base/tests/textarea-maxlength-valid-change.html
new file mode 100644
index 0000000000..24007a500f
--- /dev/null
+++ b/layout/base/tests/textarea-maxlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with maxlength is valid if the user edits and it's not too long -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ synthesizeKey("KEY_Backspace"); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ synthesizeKey("KEY_Backspace");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" maxlength="3">foooo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-invalid-change.html b/layout/base/tests/textarea-minlength-invalid-change.html
new file mode 100644
index 0000000000..9267ccfcaf
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ sendString("o");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="4">fo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-ui-invalid-change.html b/layout/base/tests/textarea-minlength-ui-invalid-change.html
new file mode 100644
index 0000000000..e52fba71e2
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-ui-invalid-change.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is -moz-ui-invalid if the user edits and it's too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { box-shadow:none; background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ sendString("o");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="4">fo</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-ui-valid-change.html b/layout/base/tests/textarea-minlength-ui-valid-change.html
new file mode 100644
index 0000000000..18cecef138
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-ui-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is -moz-ui-valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :-moz-ui-valid { background-color:green; }
+ :-moz-ui-invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ sendString("o"); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ sendString("o");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="3">f</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-minlength-valid-before-change.html b/layout/base/tests/textarea-minlength-valid-before-change.html
new file mode 100644
index 0000000000..6fd7ad9799
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-valid-before-change.html
@@ -0,0 +1,15 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is valid until the user edits it, even if it's too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ </head>
+ <body onload="document.documentElement.className=''">
+ <textarea id="textarea" minlength="5">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/textarea-minlength-valid-change.html b/layout/base/tests/textarea-minlength-valid-change.html
new file mode 100644
index 0000000000..95faa54763
--- /dev/null
+++ b/layout/base/tests/textarea-minlength-valid-change.html
@@ -0,0 +1,28 @@
+<!DOCTYPE HTML>
+<html>
+ <!-- Test: textarea with minlength is valid if the user edits and it's not too short -->
+ <head>
+ <style>
+ :valid { background-color:green; }
+ :invalid { background-color:red; }
+ * { background-color:white; }
+ </style>
+ <script src="/tests/SimpleTest/EventUtils.js"></script>
+ <script>
+ function runTest() {
+ var textarea = document.getElementById('textarea');
+ textarea.setSelectionRange(textarea.value.length, textarea.value.length)
+ textarea.focus();
+ sendString("o"); // so that it becomes invalid first
+ textarea.blur();
+ textarea.focus();
+ sendString("o");
+ textarea.blur(); // to hide the caret
+ document.documentElement.className='';
+ }
+ </script>
+ </head>
+ <body onload="runTest()">
+ <textarea id="textarea" minlength="3">f</textarea>
+ </body>
+</html>
diff --git a/layout/base/tests/textarea-valid-ref.html b/layout/base/tests/textarea-valid-ref.html
new file mode 100644
index 0000000000..547b4fb7c5
--- /dev/null
+++ b/layout/base/tests/textarea-valid-ref.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<html>
+ <body>
+ <textarea style="background-color:green">foo</textarea>
+ </body>
+</html>
+
diff --git a/layout/base/tests/transformed_scrolling_repaints_3_window.html b/layout/base/tests/transformed_scrolling_repaints_3_window.html
new file mode 100644
index 0000000000..b15e3382fe
--- /dev/null
+++ b/layout/base/tests/transformed_scrolling_repaints_3_window.html
@@ -0,0 +1,46 @@
+<!DOCTYPE HTML>
+<html style="overflow: hidden;">
+<head>
+ <title>Test that scaled elements with scrolled contents don't repaint unnecessarily when we scroll inside them</title>
+ <script src="/tests/SimpleTest/paint_listener.js"></script>
+</head>
+<!-- Need a timeout here to allow paint unsuppression before we start the test -->
+<body onload="setTimeout(startTest,0)" style="background:white;">
+<iframe id="t" style="transform: scale(0.48979); transform-origin:top left; width:500px; height:600px;"
+ src="transformed_scrolling_repaints_3_window_frame.html">
+</iframe>
+<pre id="test">
+<script type="application/javascript">
+var SimpleTest = window.opener.SimpleTest;
+var SpecialPowers = window.opener.SpecialPowers;
+var is = window.opener.is;
+var smoothScrollPref = "general.smoothScroll";
+
+function startTest() {
+ SpecialPowers.pushPrefEnv({"set":[[smoothScrollPref, false]]}, runTest);
+}
+
+async function runTest() {
+ let t = document.getElementById("t");
+ let e = t.contentDocument.getElementById("e");
+ t.contentWindow.scrollTo(0,0);
+ let utils = SpecialPowers.getDOMWindowUtils(window);
+
+ for (let i = 0; i < 15; i++) {
+ let painted = utils.checkAndClearPaintedState(e);
+ // We ignore the first few scrolls, to ensure we have triggered activity
+ // heuristics.
+ if (i >= 5) {
+ is(painted, false,
+ "Fully-visible scrolled element should not have been painted");
+ }
+ t.contentWindow.scrollByLines(1);
+ await promiseAllPaintsDone(null, true);
+ }
+ SimpleTest.finish();
+ window.close();
+}
+</script>
+</pre>
+</body>
+</html>
diff --git a/layout/base/tests/transformed_scrolling_repaints_3_window_frame.html b/layout/base/tests/transformed_scrolling_repaints_3_window_frame.html
new file mode 100644
index 0000000000..a3213908d6
--- /dev/null
+++ b/layout/base/tests/transformed_scrolling_repaints_3_window_frame.html
@@ -0,0 +1,58 @@
+<body style='background:yellow;'>
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p id='e'>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+<p>My graduate adviser was the most patient, understanding, and helpful
+person I've ever had the joy of dealing with. That doesn't change that
+there are some real dicks out there, and some of them happen to be
+scientists.
+</body>
diff --git a/layout/base/tests/visual_viewport_in_child.html b/layout/base/tests/visual_viewport_in_child.html
new file mode 100644
index 0000000000..3fd47cb6bd
--- /dev/null
+++ b/layout/base/tests/visual_viewport_in_child.html
@@ -0,0 +1,25 @@
+<!DOCTYPE html>
+<meta charset=utf-8>
+<meta name="viewport" content="width=device-width, minimum-scale=0.25">
+<style>
+html {
+ overflow: hidden;
+}
+html, body {
+ margin: 0;
+ width: 100%;
+ height: 100%;
+}
+div {
+ position: absolute;
+}
+</style>
+<div style="background: red; width: 300%; height: 300%;"></div>
+<div style="background: green; width: 200%; height: 200%;"></div>
+<div style="background: blue; width: 100%; height: 100%;"></div>
+<script>
+window.onload = () => {
+ parent.postMessage({ width: window.visualViewport.width,
+ height: window.visualViewport.height } , "*");
+};
+</script>