From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- layout/base/AccessibleCaret.cpp | 378 + layout/base/AccessibleCaret.h | 230 + layout/base/AccessibleCaretEventHub.cpp | 702 + layout/base/AccessibleCaretEventHub.h | 241 + layout/base/AccessibleCaretLogger.h | 28 + layout/base/AccessibleCaretManager.cpp | 1506 ++ layout/base/AccessibleCaretManager.h | 442 + layout/base/ArenaObjectID.h | 25 + layout/base/AutoProfilerStyleMarker.h | 95 + layout/base/Baseline.cpp | 105 + layout/base/Baseline.h | 78 + layout/base/CaretAssociationHint.h | 21 + layout/base/ContainStyleScopeManager.cpp | 242 + layout/base/ContainStyleScopeManager.h | 139 + layout/base/DepthOrderedFrameList.cpp | 63 + layout/base/DepthOrderedFrameList.h | 64 + layout/base/DisplayPortUtils.cpp | 977 + layout/base/DisplayPortUtils.h | 312 + layout/base/FrameProperties.h | 435 + layout/base/GeckoMVMContext.cpp | 217 + layout/base/GeckoMVMContext.h | 70 + layout/base/GeometryUtils.cpp | 498 + layout/base/GeometryUtils.h | 68 + layout/base/LayoutConstants.h | 108 + layout/base/LayoutLogging.cpp | 29 + layout/base/LayoutLogging.h | 64 + layout/base/LayoutTelemetryTools.cpp | 177 + layout/base/LayoutTelemetryTools.h | 90 + layout/base/MVMContext.h | 69 + layout/base/MediaEmulationData.h | 26 + layout/base/MobileViewportManager.cpp | 757 + layout/base/MobileViewportManager.h | 220 + layout/base/MotionPathUtils.cpp | 762 + layout/base/MotionPathUtils.h | 264 + layout/base/OverflowChangedTracker.h | 208 + layout/base/PositionedEventTargeting.cpp | 619 + layout/base/PositionedEventTargeting.h | 41 + layout/base/PresShell.cpp | 12203 ++++++++++ layout/base/PresShell.h | 3287 +++ layout/base/PresShellForwards.h | 245 + layout/base/PresShellInlines.h | 83 + layout/base/PresState.ipdlh | 60 + layout/base/RelativeTo.h | 51 + layout/base/RestyleManager.cpp | 3881 ++++ layout/base/RestyleManager.h | 606 + layout/base/ScrollStyles.cpp | 48 + layout/base/ScrollStyles.h | 44 + layout/base/ScrollTypes.h | 73 + layout/base/ShapeUtils.cpp | 249 + layout/base/ShapeUtils.h | 151 + layout/base/StackArena.cpp | 172 + layout/base/StackArena.h | 92 + layout/base/StaticPresData.cpp | 263 + layout/base/StaticPresData.h | 176 + layout/base/SurfaceFromElementResult.h | 104 + layout/base/TouchManager.cpp | 476 + layout/base/TouchManager.h | 84 + layout/base/UnitTransforms.h | 388 + layout/base/Units.h | 968 + layout/base/ViewportUtils.cpp | 296 + layout/base/ViewportUtils.h | 128 + layout/base/WordMovementType.h | 16 + layout/base/ZoomConstraintsClient.cpp | 281 + layout/base/ZoomConstraintsClient.h | 55 + layout/base/crashtests/1001237.html | 10 + layout/base/crashtests/1009036.html | 15 + layout/base/crashtests/1043163-1.html | 2 + layout/base/crashtests/1061028.html | 9 + layout/base/crashtests/1107508-1.html | 18 + layout/base/crashtests/1116104.html | 15 + layout/base/crashtests/1127198-1.html | 5 + layout/base/crashtests/1140198.html | 16 + layout/base/crashtests/1143535.html | 6 + layout/base/crashtests/1153716.html | 19 + layout/base/crashtests/1156588.html | 26 + layout/base/crashtests/1162813.xhtml | 17 + layout/base/crashtests/1163583.html | 14 + layout/base/crashtests/118931-1.html | 7 + layout/base/crashtests/121533-1.html | 11 + layout/base/crashtests/123049-1.html | 12 + layout/base/crashtests/1234622-1.html | 17 + layout/base/crashtests/1235467-1.html | 8 + layout/base/crashtests/123946-1.html | 10 + layout/base/crashtests/1261351-iframe.html | 28 + layout/base/crashtests/1261351.html | 7 + layout/base/crashtests/1270797-1.html | 9 + layout/base/crashtests/1270797-1.jpg | Bin 0 -> 3595 bytes layout/base/crashtests/1278455-1.html | 11 + layout/base/crashtests/1286889.html | 2 + layout/base/crashtests/128855-1.html | 8 + layout/base/crashtests/1288608.html | 18 + layout/base/crashtests/1288946-1.html | 9 + layout/base/crashtests/1288946-2a.html | 13 + layout/base/crashtests/1288946-2b.html | 13 + layout/base/crashtests/1297835.html | 6 + layout/base/crashtests/1299736-1.html | 15 + layout/base/crashtests/1308793.svg | 31 + layout/base/crashtests/1308848-1.html | 10 + layout/base/crashtests/1308848-2.html | 10 + layout/base/crashtests/133410-1.html | 27 + layout/base/crashtests/1338772-1.html | 42 + layout/base/crashtests/1340571.html | 15 + layout/base/crashtests/1343139-1.html | 20 + layout/base/crashtests/1343606.html | 39 + layout/base/crashtests/1343937.html | 13 + layout/base/crashtests/1352380.html | 9 + layout/base/crashtests/1362423-1.html | 16 + layout/base/crashtests/1381323.html | 10 + layout/base/crashtests/1382534.html | 26 + layout/base/crashtests/1388625-1.html | 10 + layout/base/crashtests/1390389.html | 19 + layout/base/crashtests/1391736.html | 23 + layout/base/crashtests/1395591-1.html | 11 + layout/base/crashtests/1395715-1.html | 17 + layout/base/crashtests/1397398-1.html | 20 + layout/base/crashtests/1397398-2.html | 20 + layout/base/crashtests/1397398-3.html | 8 + layout/base/crashtests/1398500.html | 21 + layout/base/crashtests/1400438-1.html | 9 + layout/base/crashtests/1400599-1.html | 7 + layout/base/crashtests/1401739.html | 11 + layout/base/crashtests/1401840.html | 10 + layout/base/crashtests/1402476.html | 13 + layout/base/crashtests/1404789-2.html | 2 + layout/base/crashtests/1406562.html | 15 + layout/base/crashtests/1409147.html | 20 + layout/base/crashtests/1411138.html | 13 + layout/base/crashtests/1414100.html | 9 + layout/base/crashtests/1414303.html | 11 + layout/base/crashtests/1419762.html | 15 + layout/base/crashtests/1419802.html | 9 + layout/base/crashtests/1420533.html | 12 + layout/base/crashtests/1422908.html | 11 + layout/base/crashtests/1425893.html | 6 + layout/base/crashtests/1425959.html | 12 + layout/base/crashtests/1428353.html | 15 + layout/base/crashtests/1428892.html | 15 + layout/base/crashtests/1429088.html | 8 + layout/base/crashtests/1429961.html | 16 + layout/base/crashtests/1429962.html | 12 + layout/base/crashtests/1435015.html | 9 + layout/base/crashtests/1437155.html | 13 + layout/base/crashtests/143862-1a-inner.html | 19 + layout/base/crashtests/143862-1a.html | 7 + layout/base/crashtests/143862-1b-inner.html | 17 + layout/base/crashtests/143862-1b.html | 7 + layout/base/crashtests/143862-1c-inner.html | 17 + layout/base/crashtests/143862-1c.html | 7 + layout/base/crashtests/143862-2.html | 15 + layout/base/crashtests/1439016.html | 18 + layout/base/crashtests/1442018-1.html | 45 + layout/base/crashtests/1442506.html | 10 + layout/base/crashtests/1443027-1.html | 20 + layout/base/crashtests/1448841-1.html | 17 + layout/base/crashtests/1452839.html | 8 + layout/base/crashtests/1453196.html | 15 + layout/base/crashtests/1453342.html | 30 + layout/base/crashtests/1453702.html | 16 + layout/base/crashtests/1458121.html | 23 + layout/base/crashtests/1461749.html | 19 + layout/base/crashtests/1461812.html | 19 + layout/base/crashtests/1462412.html | 9 + layout/base/crashtests/1463940.html | 24 + layout/base/crashtests/1464641.html | 25 + layout/base/crashtests/1464737.html | 7 + layout/base/crashtests/1466638.html | 13 + layout/base/crashtests/1467519.html | 10 + layout/base/crashtests/1467688.html | 16 + layout/base/crashtests/1467964.html | 2 + layout/base/crashtests/1469354.html | 16 + layout/base/crashtests/1470499.html | 22 + layout/base/crashtests/1472020.html | 11 + layout/base/crashtests/1472027.html | 7 + layout/base/crashtests/147320-1.html | 7 + layout/base/crashtests/1477847.html | 17 + layout/base/crashtests/148245-1.html | 11 + layout/base/crashtests/1486521.html | 11 + layout/base/crashtests/1489149.html | 52 + layout/base/crashtests/1490037.html | 12 + layout/base/crashtests/149014-1.html | 44 + layout/base/crashtests/1494030.html | 22 + layout/base/crashtests/1494332.html | 6 + layout/base/crashtests/150431-1.html | 7 + layout/base/crashtests/1505420.html | 19 + layout/base/crashtests/1506163.html | 7 + layout/base/crashtests/1506204.html | 17 + layout/base/crashtests/1506314.html | 15 + layout/base/crashtests/1507244.html | 25 + layout/base/crashtests/1510080.html | 13 + layout/base/crashtests/1510485.html | 7 + layout/base/crashtests/1511442.html | 20 + layout/base/crashtests/1511535.html | 9 + layout/base/crashtests/1511563.html | 8 + layout/base/crashtests/1516286-empty-mask.html | 14 + layout/base/crashtests/1524382.html | 12 + layout/base/crashtests/1524411.html | 15 + layout/base/crashtests/1533885.html | 21 + layout/base/crashtests/1534146.html | 15 + layout/base/crashtests/1535945.html | 26 + layout/base/crashtests/1539017.html | 15 + layout/base/crashtests/1539303-iframe.html | 34 + layout/base/crashtests/1539303.html | 13 + layout/base/crashtests/1541679.html | 20 + layout/base/crashtests/1547261.html | 3 + layout/base/crashtests/1547391.html | 15 + layout/base/crashtests/1548057.html | 42 + layout/base/crashtests/1549867.html | 13 + layout/base/crashtests/1553874.html | 17 + layout/base/crashtests/1560328.html | 12 + layout/base/crashtests/1566672.html | 20 + layout/base/crashtests/1574101-1.html | 16 + layout/base/crashtests/1574101-2.html | 10 + layout/base/crashtests/1575908-1.html | 32 + layout/base/crashtests/1576972-1.html | 23 + layout/base/crashtests/1578844-1.html | 14 + layout/base/crashtests/1578844-2.html | 20 + layout/base/crashtests/1579953-1.html | 16 + layout/base/crashtests/1580576.html | 11 + layout/base/crashtests/1586600.html | 5 + layout/base/crashtests/1599518.html | 9 + layout/base/crashtests/1599532.html | 1 + layout/base/crashtests/1606492.html | 21 + layout/base/crashtests/1654315.html | 13 + layout/base/crashtests/1676301-1.html | 16 + layout/base/crashtests/1685146.html | 17 + layout/base/crashtests/1689371.html | 5 + layout/base/crashtests/1689912.html | 19 + layout/base/crashtests/1690163.html | 7 + layout/base/crashtests/1723200.html | 22 + layout/base/crashtests/1729578.html | 26 + layout/base/crashtests/1729581.html | 12 + layout/base/crashtests/1734007.html | 15 + layout/base/crashtests/1745860.html | 14 + layout/base/crashtests/1746989.html | 11 + layout/base/crashtests/1747277-1.html | 21 + layout/base/crashtests/1752649.html | 4 + layout/base/crashtests/1753779.html | 16 + layout/base/crashtests/1755790.html | 31 + layout/base/crashtests/176915-1.html | 10 + layout/base/crashtests/1771503.html | 22 + layout/base/crashtests/1789934.html | 16 + layout/base/crashtests/1791883.html | 17 + layout/base/crashtests/1797995.html | 13 + layout/base/crashtests/1818036.html | 13 + layout/base/crashtests/1819239.html | 33 + layout/base/crashtests/1821469.html | 4 + layout/base/crashtests/1849898-1.html | 40 + layout/base/crashtests/191272-1.html | 13 + layout/base/crashtests/199696-1.html | 33 + layout/base/crashtests/217903-1.html | 5 + layout/base/crashtests/223064-1.html | 11 + layout/base/crashtests/234851-1.html | 14 + layout/base/crashtests/234851-2.html | 35 + layout/base/crashtests/241300-1.html | 5 + layout/base/crashtests/243159-1.html | 4 + layout/base/crashtests/243159-2.xhtml | 26 + layout/base/crashtests/243519-1.html | 30 + layout/base/crashtests/244490-1.html | 16 + layout/base/crashtests/254367-1.html | 6 + layout/base/crashtests/263359-1.html | 28 + layout/base/crashtests/265027-1.html | 19 + layout/base/crashtests/265736-1.html | 2 + layout/base/crashtests/265736-2.html | 8 + layout/base/crashtests/265899-1.html | 5 + layout/base/crashtests/265973-1.html | 8 + layout/base/crashtests/265986-1.html | 10 + layout/base/crashtests/265999-1.html | 8 + layout/base/crashtests/266222-1.html | 7 + layout/base/crashtests/266360-1.html | 9 + layout/base/crashtests/266445-1.html | 9 + layout/base/crashtests/266445-2.html | 9 + layout/base/crashtests/268157-1.html | 15 + layout/base/crashtests/269566-1.html | 11 + layout/base/crashtests/272647-1.html | 18 + layout/base/crashtests/275746-1.html | 9 + layout/base/crashtests/276053-1.html | 21 + layout/base/crashtests/280708-1.html | 9 + layout/base/crashtests/280708-2.html | 9 + layout/base/crashtests/281333-1.html | 1 + layout/base/crashtests/285212-1.html | 13 + layout/base/crashtests/286813-1.html | 9 + layout/base/crashtests/306940-1.html | 49 + layout/base/crashtests/310267-1.xml | 32 + layout/base/crashtests/310638-1.svg | 38 + layout/base/crashtests/310638-2.html | 19 + layout/base/crashtests/311661-1.xhtml | 31 + layout/base/crashtests/311661-2.xhtml | 28 + layout/base/crashtests/313086-1.xml | 28 + layout/base/crashtests/317285-1.html | 1 + layout/base/crashtests/317934-1-inner.html | 31 + layout/base/crashtests/317934-1.html | 9 + layout/base/crashtests/320459-1.html | 7 + layout/base/crashtests/321058-1.xhtml | 4 + layout/base/crashtests/321058-2.xhtml | 25 + layout/base/crashtests/321077-1.xhtml | 6 + layout/base/crashtests/321077-2.xhtml | 22 + layout/base/crashtests/322436-1.html | 31 + layout/base/crashtests/322678.html | 27 + layout/base/crashtests/325024.html | 20 + layout/base/crashtests/325218.xhtml | 25 + layout/base/crashtests/325967-1.html | 29 + layout/base/crashtests/325984-1.xhtml | 5 + layout/base/crashtests/325984-2.html | 31 + layout/base/crashtests/328944-1.xhtml | 23 + layout/base/crashtests/329900-1.html | 15 + layout/base/crashtests/330015-1.html | 14 + layout/base/crashtests/331679-1.xhtml | 36 + layout/base/crashtests/331679-2.xml | 19 + layout/base/crashtests/331679-3.xml | 19 + layout/base/crashtests/331883-1-inner.html | 30 + layout/base/crashtests/331883-1.html | 16 + layout/base/crashtests/335140-1.html | 12 + layout/base/crashtests/336291-1.html | 19 + layout/base/crashtests/336999-1.xhtml | 24 + layout/base/crashtests/337066-1.xhtml | 22 + layout/base/crashtests/337268-1.html | 45 + layout/base/crashtests/337419-1.html | 23 + layout/base/crashtests/337476-1.xhtml | 32 + layout/base/crashtests/338703-1.html | 29 + layout/base/crashtests/339651-1.html | 37 + layout/base/crashtests/340093-1.xhtml | 11 + layout/base/crashtests/341382-1.html | 22 + layout/base/crashtests/341382-2.html | 9 + layout/base/crashtests/341858-1.html | 14 + layout/base/crashtests/342145-1.xhtml | 26 + layout/base/crashtests/343293-1.xhtml | 19 + layout/base/crashtests/343293-2.xhtml | 14 + layout/base/crashtests/343540-1.html | 26 + layout/base/crashtests/344057-1.xhtml | 9 + layout/base/crashtests/344064-1-inner.xhtml | 13 + layout/base/crashtests/344064-1.html | 9 + layout/base/crashtests/344300-1-inner.xhtml | 36 + layout/base/crashtests/344300-1.html | 9 + layout/base/crashtests/344340-1.xhtml | 28 + layout/base/crashtests/347898-1.html | 9 + layout/base/crashtests/348126-1-inner.html | 28 + layout/base/crashtests/348126-1.gif | Bin 0 -> 980 bytes layout/base/crashtests/348126-1.html | 9 + layout/base/crashtests/348688-1.html | 24 + layout/base/crashtests/348708-1.xhtml | 20 + layout/base/crashtests/348729-1-inner.html | 29 + layout/base/crashtests/348729-1.html | 6 + layout/base/crashtests/349095-1.xhtml | 25 + layout/base/crashtests/350267-1.html | 2 + layout/base/crashtests/354133-1-inner.xhtml | 22 + layout/base/crashtests/354133-1.html | 9 + layout/base/crashtests/354766-1.xhtml | 19 + layout/base/crashtests/355989-1.xhtml | 27 + layout/base/crashtests/355993-1.xhtml | 26 + layout/base/crashtests/356325-1.xhtml | 20 + layout/base/crashtests/358729-1.xhtml | 52 + layout/base/crashtests/360339-1.xhtml | 23 + layout/base/crashtests/360339-2.xhtml | 28 + layout/base/crashtests/363729-1.html | 3 + layout/base/crashtests/363729-2.html | 18 + layout/base/crashtests/363729-3.html | 20 + layout/base/crashtests/365909-1.xhtml | 10 + layout/base/crashtests/365909-2.xhtml | 10 + layout/base/crashtests/366128-1.xhtml | 32 + layout/base/crashtests/366271-1-frame.svg | 13 + layout/base/crashtests/366271-1.html | 21 + layout/base/crashtests/366967-1.html | 33 + layout/base/crashtests/367015-1.html | 22 + layout/base/crashtests/367243-1.html | 37 + layout/base/crashtests/369176-1.html | 37 + layout/base/crashtests/369547-1.html | 50 + layout/base/crashtests/369547-2.html | 15 + layout/base/crashtests/369945-1.xhtml | 42 + layout/base/crashtests/371681-1.xhtml | 22 + layout/base/crashtests/372237-1.html | 33 + layout/base/crashtests/372550-1.html | 17 + layout/base/crashtests/373628-1.html | 16 + layout/base/crashtests/373628.html | 929 + layout/base/crashtests/374297-1.html | 20 + layout/base/crashtests/374297-2.html | 23 + layout/base/crashtests/378325-1.html | 26 + layout/base/crashtests/378682.html | 9 + layout/base/crashtests/379419-1.xhtml | 12 + layout/base/crashtests/379799-1.html | 31 + layout/base/crashtests/380096-1.html | 4 + layout/base/crashtests/382204-1.html | 21 + layout/base/crashtests/383129-1-inner.xhtml | 22 + layout/base/crashtests/383129-1.html | 9 + layout/base/crashtests/384344-1-inner.html | 20 + layout/base/crashtests/384344-1.html | 9 + layout/base/crashtests/384392-1.xhtml | 27 + layout/base/crashtests/384392-2.svg | 3 + layout/base/crashtests/384649-1.xhtml | 31 + layout/base/crashtests/385354.html | 18 + layout/base/crashtests/385866-1.xhtml | 23 + layout/base/crashtests/385880-1.xhtml | 8 + layout/base/crashtests/386266-1.html | 28 + layout/base/crashtests/386476.html | 12 + layout/base/crashtests/387195-1.html | 7 + layout/base/crashtests/387195-2.xhtml | 23 + layout/base/crashtests/388715-1.html | 22 + layout/base/crashtests/390976-1.html | 22 + layout/base/crashtests/393661-1.html | 20 + layout/base/crashtests/393801-1-inner.html | 781 + layout/base/crashtests/393801-1.html | 7 + layout/base/crashtests/394150-1.xhtml | 27 + layout/base/crashtests/397011-1.xhtml | 13 + layout/base/crashtests/398510-1.xhtml | 22 + layout/base/crashtests/398733-1.html | 20 + layout/base/crashtests/398733-2.html | 9 + layout/base/crashtests/399132-1.xhtml | 16 + layout/base/crashtests/399219-1.xhtml | 17 + layout/base/crashtests/399365-1.html | 16 + layout/base/crashtests/399676-1.xhtml | 7 + layout/base/crashtests/399687-1.html | 38 + layout/base/crashtests/399940-1.xhtml | 21 + layout/base/crashtests/399951-1.html | 14 + layout/base/crashtests/399994-1.html | 11 + layout/base/crashtests/400445-1.xhtml | 22 + layout/base/crashtests/400904-1.xhtml | 20 + layout/base/crashtests/401734-1.html | 17 + layout/base/crashtests/401734-2.html | 17 + layout/base/crashtests/403048.html | 10 + layout/base/crashtests/403175-1.html | 30 + layout/base/crashtests/403245-1.html | 16 + layout/base/crashtests/403454.html | 37 + layout/base/crashtests/403569-1.xhtml | 29 + layout/base/crashtests/403569-2.xhtml | 19 + layout/base/crashtests/403569-3.xhtml | 25 + layout/base/crashtests/404491-1.html | 5 + layout/base/crashtests/404721-1.xhtml | 17 + layout/base/crashtests/404721-2.xhtml | 18 + layout/base/crashtests/405049-1.xhtml | 3 + layout/base/crashtests/406675-1.html | 17 + layout/base/crashtests/408292.html | 18 + layout/base/crashtests/408299.html | 12 + layout/base/crashtests/408450-1.xhtml | 7 + layout/base/crashtests/409461-1.xhtml | 15 + layout/base/crashtests/410967.html | 17 + layout/base/crashtests/411870-1.html | 18 + layout/base/crashtests/412651-1-frame.xhtml | 29 + layout/base/crashtests/412651-1.html | 21 + layout/base/crashtests/413587-1.svg | 11 + layout/base/crashtests/415503.xhtml | 28 + layout/base/crashtests/416107.xhtml | 26 + layout/base/crashtests/419985.html | 29 + layout/base/crashtests/420031-1.html | 8 + layout/base/crashtests/420213-1.html | 6 + layout/base/crashtests/420219-1.html | 22 + layout/base/crashtests/420651-1.xhtml | 4 + layout/base/crashtests/421203-1.xhtml | 5 + layout/base/crashtests/421432.html | 14 + layout/base/crashtests/422276.html | 18 + layout/base/crashtests/423107-1.xhtml | 19 + layout/base/crashtests/425981-1.html | 18 + layout/base/crashtests/428138-1.html | 24 + layout/base/crashtests/428448-1.html | 9 + layout/base/crashtests/429088-1.html | 19 + layout/base/crashtests/429088-2.html | 25 + layout/base/crashtests/429865-1.html | 14 + layout/base/crashtests/429881.html | 6 + layout/base/crashtests/430569-1.html | 3 + layout/base/crashtests/430569-2.html | 11 + layout/base/crashtests/432752-1.svg | 27 + layout/base/crashtests/433450-1.html | 19 + layout/base/crashtests/436982-1.html | 7 + layout/base/crashtests/437142-1.html | 25 + layout/base/crashtests/439258-1.html | 20 + layout/base/crashtests/439343.html | 2 + layout/base/crashtests/444863-1.html | 25 + layout/base/crashtests/444925-1.xhtml | 10 + layout/base/crashtests/444967-1.html | 12 + layout/base/crashtests/446328-iframe.html | 1 + layout/base/crashtests/446328-top.html | 21 + layout/base/crashtests/446328.gif | Bin 0 -> 85 bytes layout/base/crashtests/446328.html | 12 + layout/base/crashtests/448488-1.html | 4 + layout/base/crashtests/448543-1.html | 8 + layout/base/crashtests/448543-2.html | 1 + layout/base/crashtests/448543-3.html | 7 + layout/base/crashtests/450319-1.xhtml | 32 + layout/base/crashtests/453894-1.xhtml | 15 + layout/base/crashtests/454751-1.xhtml | 20 + layout/base/crashtests/455063-1.html | 6 + layout/base/crashtests/455063-2.html | 6 + layout/base/crashtests/455063-3.html | 6 + layout/base/crashtests/455171-4.html | 8 + layout/base/crashtests/455623-1.html | 19 + layout/base/crashtests/457362-1.xhtml | 9 + layout/base/crashtests/457514.html | 27 + layout/base/crashtests/460389-1.html | 6 + layout/base/crashtests/46043-1.html | 12 + layout/base/crashtests/462392.html | 43 + layout/base/crashtests/466763-1.html | 24 + layout/base/crashtests/467881-1.html | 47 + layout/base/crashtests/468491-1.html | 16 + layout/base/crashtests/468555-1.xhtml | 9 + layout/base/crashtests/468563-1.html | 7 + layout/base/crashtests/468578-1.xhtml | 21 + layout/base/crashtests/468645-3.xhtml | 5 + layout/base/crashtests/469861-1.xhtml | 15 + layout/base/crashtests/469861-2.xhtml | 15 + layout/base/crashtests/470851-1.xhtml | 13 + layout/base/crashtests/473042.xhtml | 1 + layout/base/crashtests/474075.html | 12 + layout/base/crashtests/477333-1.xhtml | 22 + layout/base/crashtests/477731-1.html | 6 + layout/base/crashtests/47843-1.html | 13 + layout/base/crashtests/479114-1.html | 14 + layout/base/crashtests/479360-1.xhtml | 16 + layout/base/crashtests/480686-1.html | 13 + layout/base/crashtests/481806-1.html | 14 + layout/base/crashtests/483604-1.xhtml | 6 + layout/base/crashtests/485501-1.html | 4 + layout/base/crashtests/488390-1.xhtml | 18 + layout/base/crashtests/489691.html | 20 + layout/base/crashtests/490376-1.xhtml | 15 + layout/base/crashtests/490559-1.html | 16 + layout/base/crashtests/490747.html | 8 + layout/base/crashtests/49122-1.html | 20 + layout/base/crashtests/491547-1.xhtml | 20 + layout/base/crashtests/491547-2.xhtml | 31 + layout/base/crashtests/492014.xhtml | 4 + layout/base/crashtests/492112-1.xhtml | 14 + layout/base/crashtests/492163-1.xhtml | 21 + layout/base/crashtests/495350-1.html | 9 + layout/base/crashtests/496011-1.xhtml | 20 + layout/base/crashtests/499741-1.xhtml | 1 + layout/base/crashtests/499841-1.xhtml | 5 + layout/base/crashtests/499858-1.xhtml | 5 + layout/base/crashtests/500467-1.html | 23141 +++++++++++++++++++ layout/base/crashtests/501878-1.html | 5 + layout/base/crashtests/50257-1.html | 20 + layout/base/crashtests/503936-1.html | 29 + layout/base/crashtests/50395-1.html | 24 + layout/base/crashtests/507119.html | 554 + layout/base/crashtests/522374-1.html | 21 + layout/base/crashtests/522374-2.html | 21 + layout/base/crashtests/526378-1.xhtml | 28 + layout/base/crashtests/534367-1.xhtml | 29 + layout/base/crashtests/534368-1.xhtml | 14 + layout/base/crashtests/534768-1.html | 23 + layout/base/crashtests/534768-2.html | 22 + layout/base/crashtests/535721-1.xhtml | 17 + layout/base/crashtests/535911-1.xhtml | 16 + layout/base/crashtests/536720.xhtml | 23 + layout/base/crashtests/537562-1.xhtml | 10 + layout/base/crashtests/537624-1.html | 18 + layout/base/crashtests/537631-1.html | 5 + layout/base/crashtests/538082-1.xhtml | 34 + layout/base/crashtests/538207-1.xhtml | 14 + layout/base/crashtests/538210-1.html | 16 + layout/base/crashtests/538267-1.html | 18 + layout/base/crashtests/540760.xhtml | 18 + layout/base/crashtests/540771-1.xhtml | 18 + layout/base/crashtests/541869-1.xhtml | 5 + layout/base/crashtests/541869-2.html | 5 + layout/base/crashtests/543648-1.html | 1 + layout/base/crashtests/560447-1.html | 1 + layout/base/crashtests/564063-1.html | 20 + layout/base/crashtests/56746-1.html | 16 + layout/base/crashtests/569018-1.html | 17 + layout/base/crashtests/572003.xhtml | 3 + layout/base/crashtests/572582-1.xhtml | 25 + layout/base/crashtests/576649-1.html | 4 + layout/base/crashtests/579655.html | 26 + layout/base/crashtests/580129-1.html | 19 + layout/base/crashtests/580494-1.html | 1 + layout/base/crashtests/580834-1.xhtml | 5 + layout/base/crashtests/589787.html | 27 + layout/base/crashtests/591075-1.html | 2 + layout/base/crashtests/591998-1.html | 2 + layout/base/crashtests/595039-1.html | 1 + layout/base/crashtests/597924-1.html | 16 + layout/base/crashtests/606432-1.html | 24 + layout/base/crashtests/609821-1.xhtml | 17 + layout/base/crashtests/615146-1.html | 1 + layout/base/crashtests/615781-1.xhtml | 22 + .../616495-single-side-composite-color-border.html | 21 + layout/base/crashtests/629035-1.html | 3 + layout/base/crashtests/629908-1.html | 9 + layout/base/crashtests/635329.html | 18 + layout/base/crashtests/636229-1.html | 2 + layout/base/crashtests/640272-empty.html | 0 layout/base/crashtests/640272-ref.html | 14 + layout/base/crashtests/640272.html | 15 + layout/base/crashtests/645193.html | 15 + layout/base/crashtests/645572-1.html | 52 + layout/base/crashtests/650475.xhtml | 14 + layout/base/crashtests/650489.xhtml | 3 + layout/base/crashtests/651342-1.html | 4 + layout/base/crashtests/653133-1.html | 17 + layout/base/crashtests/663295.html | 2 + layout/base/crashtests/663662-1.html | 1 + layout/base/crashtests/663662-2.html | 1 + layout/base/crashtests/665837.html | 13 + layout/base/crashtests/668579.html | 10 + layout/base/crashtests/668941.xhtml | 16 + layout/base/crashtests/670226.html | 10 + layout/base/crashtests/675246-1.xhtml | 8 + layout/base/crashtests/690247-1.html | 2 + layout/base/crashtests/690619-1.html | 1 + layout/base/crashtests/691118-1.html | 24 + layout/base/crashtests/695861.html | 9 + layout/base/crashtests/695964-1.svg | 1 + layout/base/crashtests/698335.html | 2 + layout/base/crashtests/699353-1.html | 18 + layout/base/crashtests/701504.html | 24 + layout/base/crashtests/707098.html | 6 + layout/base/crashtests/709536-1.xhtml | 1 + layout/base/crashtests/722137.html | 18 + layout/base/crashtests/725535.html | 8 + layout/base/crashtests/727601.html | 3 + layout/base/crashtests/735943.html | 38 + layout/base/crashtests/736389-1.xhtml | 47 + layout/base/crashtests/736924-1.html | 23 + layout/base/crashtests/749816-1.html | 15 + layout/base/crashtests/763223-1.html | 6 + layout/base/crashtests/763702.xhtml | 9 + layout/base/crashtests/767593-1.html | 7 + layout/base/crashtests/767593-2.html | 7 + layout/base/crashtests/770381-1.html | 12 + layout/base/crashtests/772306.html | 40 + layout/base/crashtests/788360.html | 6 + layout/base/crashtests/793848.html | 28 + layout/base/crashtests/795646.html | 7 + layout/base/crashtests/802902.html | 10 + layout/base/crashtests/806056-1.html | 16 + layout/base/crashtests/806056-2.html | 18 + layout/base/crashtests/812665.html | 6 + layout/base/crashtests/813372-1.html | 52 + layout/base/crashtests/817219-iframe.html | 35 + layout/base/crashtests/817219.html | 22 + layout/base/crashtests/818454.html | 24 + layout/base/crashtests/822865.html | 4 + layout/base/crashtests/824300.html | 13 + layout/base/crashtests/824862.html | 5 + layout/base/crashtests/826163.html | 11 + layout/base/crashtests/827192.html | 9 + layout/base/crashtests/830138-1.html | 17 + layout/base/crashtests/830192-1.html | 31 + layout/base/crashtests/830299-1.html | 27 + layout/base/crashtests/833604-1.html | 18 + layout/base/crashtests/835056.html | 19 + layout/base/crashtests/836990-1.html | 12 + layout/base/crashtests/840480.html | 44 + layout/base/crashtests/842114.html | 6 + layout/base/crashtests/847242.html | 13 + layout/base/crashtests/852293.html | 67 + layout/base/crashtests/859526-1.html | 7 + layout/base/crashtests/859630-1.html | 7 + layout/base/crashtests/860579-1.html | 21 + layout/base/crashtests/866588.html | 25 + layout/base/crashtests/876092.html | 29 + layout/base/crashtests/876221.html | 39 + layout/base/crashtests/89101-1.html | 22 + layout/base/crashtests/89358-1.html | 10 + layout/base/crashtests/897852.html | 9 + layout/base/crashtests/898913.html | 24 + layout/base/crashtests/90205-1.html | 15 + layout/base/crashtests/926728.html | 13 + layout/base/crashtests/930381.html | 122 + layout/base/crashtests/931450.html | 10 + layout/base/crashtests/931460-1.html | 5 + layout/base/crashtests/931464.html | 18 + layout/base/crashtests/935765-1.html | 9 + layout/base/crashtests/936988-1.html | 9 + layout/base/crashtests/942690.html | 15 + layout/base/crashtests/973390-1.html | 7 + layout/base/crashtests/989994-1.html | 5 + layout/base/crashtests/99776-1.html | 9 + layout/base/crashtests/crashtests.list | 579 + layout/base/gtest/TestAccessibleCaretEventHub.cpp | 785 + layout/base/gtest/TestAccessibleCaretManager.cpp | 848 + layout/base/gtest/moz.build | 26 + layout/base/metrics.yaml | 50 + layout/base/moz.build | 188 + layout/base/nsAutoLayoutPhase.cpp | 90 + layout/base/nsAutoLayoutPhase.h | 49 + layout/base/nsBidiPresUtils.cpp | 2499 ++ layout/base/nsBidiPresUtils.h | 587 + layout/base/nsCSSColorUtils.cpp | 202 + layout/base/nsCSSColorUtils.h | 49 + layout/base/nsCSSFrameConstructor.cpp | 12018 ++++++++++ layout/base/nsCSSFrameConstructor.h | 2159 ++ layout/base/nsCaret.cpp | 766 + layout/base/nsCaret.h | 284 + layout/base/nsChangeHint.h | 513 + layout/base/nsCompatibility.h | 18 + layout/base/nsCounterManager.cpp | 552 + layout/base/nsCounterManager.h | 350 + layout/base/nsDocumentViewer.cpp | 3514 +++ layout/base/nsFrameManager.cpp | 250 + layout/base/nsFrameManager.h | 100 + layout/base/nsFrameTraversal.cpp | 305 + layout/base/nsFrameTraversal.h | 122 + layout/base/nsGenConList.cpp | 229 + layout/base/nsGenConList.h | 132 + layout/base/nsIDocumentViewerPrint.h | 74 + layout/base/nsILayoutHistoryState.idl | 120 + layout/base/nsIPercentBSizeObserver.h | 33 + layout/base/nsIPreloadedStyleSheet.idl | 16 + layout/base/nsIReflowCallback.h | 34 + layout/base/nsIStyleSheetService.idl | 71 + layout/base/nsLayoutDebugger.cpp | 276 + layout/base/nsLayoutHistoryState.cpp | 162 + layout/base/nsLayoutUtils.cpp | 10003 ++++++++ layout/base/nsLayoutUtils.h | 3221 +++ layout/base/nsPresArena.cpp | 185 + layout/base/nsPresArena.h | 67 + layout/base/nsPresArenaObjectList.h | 26 + layout/base/nsPresContext.cpp | 3137 +++ layout/base/nsPresContext.h | 1461 ++ layout/base/nsPresContextInlines.h | 22 + layout/base/nsQuoteList.cpp | 165 + layout/base/nsQuoteList.h | 100 + layout/base/nsRefreshDriver.cpp | 3323 +++ layout/base/nsRefreshDriver.h | 735 + layout/base/nsRefreshObservers.cpp | 55 + layout/base/nsRefreshObservers.h | 113 + layout/base/nsStyleChangeList.cpp | 72 + layout/base/nsStyleChangeList.h | 55 + layout/base/nsStyleSheetService.cpp | 337 + layout/base/nsStyleSheetService.h | 76 + layout/base/tests/Ahem.ttf | Bin 0 -> 12480 bytes layout/base/tests/accessiblecaret_magnifier.html | 91 + .../tests/border_radius_hit_testing_iframe.html | 27 + layout/base/tests/browser.toml | 61 + layout/base/tests/browser_bug1701027-1.js | 136 + layout/base/tests/browser_bug1701027-2.js | 126 + layout/base/tests/browser_bug1757410.js | 62 + layout/base/tests/browser_bug1787079.js | 88 + layout/base/tests/browser_bug1791083.js | 81 + layout/base/tests/browser_bug617076.js | 71 + .../base/tests/browser_css_registered_property.js | 93 + .../tests/browser_disableDialogs_onbeforeunload.js | 64 + ...rowser_onbeforeunload_only_after_interaction.js | 75 + ...beforeunload_only_after_interaction_in_frame.js | 96 + ...er_scroll_into_view_in_out_of_process_iframe.js | 51 + ...lect_popup_position_in_out_of_process_iframe.js | 125 + .../base/tests/browser_stylesheet_change_events.js | 227 + .../base/tests/browser_visual_viewport_iframe.js | 59 + layout/base/tests/bug1007065-1-ref.html | 15 + layout/base/tests/bug1007065-1.html | 15 + layout/base/tests/bug1007067-1-ref.html | 20 + layout/base/tests/bug1007067-1.html | 20 + layout/base/tests/bug1061468-ref.html | 13 + layout/base/tests/bug1061468.html | 40 + layout/base/tests/bug106855-1-ref.html | 27 + layout/base/tests/bug106855-1.html | 25 + layout/base/tests/bug106855-2.html | 26 + layout/base/tests/bug1078327_inner.html | 107 + layout/base/tests/bug1080360_inner.html | 83 + layout/base/tests/bug1080361_inner.html | 114 + layout/base/tests/bug1082486-1-ref.html | 18 + layout/base/tests/bug1082486-1.html | 28 + layout/base/tests/bug1082486-2-ref.html | 12 + layout/base/tests/bug1082486-2.html | 12 + layout/base/tests/bug1093686_inner.html | 83 + layout/base/tests/bug1097242-1-ref.html | 14 + layout/base/tests/bug1097242-1.html | 22 + layout/base/tests/bug1109968-1-ref.html | 17 + layout/base/tests/bug1109968-1.html | 23 + layout/base/tests/bug1109968-2-ref.html | 17 + layout/base/tests/bug1109968-2.html | 23 + layout/base/tests/bug1123067-1.html | 38 + layout/base/tests/bug1123067-2.html | 34 + layout/base/tests/bug1123067-3.html | 35 + layout/base/tests/bug1123067-ref.html | 33 + layout/base/tests/bug1132768-1-ref.html | 12 + layout/base/tests/bug1132768-1.html | 17 + layout/base/tests/bug1153130_inner.html | 72 + layout/base/tests/bug1162990_inner_1.html | 146 + layout/base/tests/bug1162990_inner_2.html | 147 + layout/base/tests/bug1226904.html | 35 + layout/base/tests/bug1237236-1-ref.html | 30 + layout/base/tests/bug1237236-1.html | 31 + layout/base/tests/bug1237236-2-ref.html | 29 + layout/base/tests/bug1237236-2.html | 30 + layout/base/tests/bug1258308-1-ref.html | 34 + layout/base/tests/bug1258308-1.html | 42 + layout/base/tests/bug1258308-2-ref.html | 30 + layout/base/tests/bug1258308-2.html | 31 + layout/base/tests/bug1259949-1-ref.html | 30 + layout/base/tests/bug1259949-1.html | 34 + layout/base/tests/bug1259949-2-ref.html | 30 + layout/base/tests/bug1259949-2.html | 30 + layout/base/tests/bug1263288-ref.html | 28 + layout/base/tests/bug1263288.html | 30 + layout/base/tests/bug1263357-1-ref.html | 28 + layout/base/tests/bug1263357-1.html | 34 + layout/base/tests/bug1263357-2-ref.html | 28 + layout/base/tests/bug1263357-2.html | 34 + layout/base/tests/bug1263357-3-ref.html | 27 + layout/base/tests/bug1263357-3.html | 28 + layout/base/tests/bug1263357-4-ref.html | 27 + layout/base/tests/bug1263357-4.html | 28 + layout/base/tests/bug1263357-5-ref.html | 27 + layout/base/tests/bug1263357-5.html | 28 + layout/base/tests/bug1354478-1-ref.html | 35 + layout/base/tests/bug1354478-1.html | 35 + layout/base/tests/bug1354478-2-ref.html | 35 + layout/base/tests/bug1354478-2.html | 35 + layout/base/tests/bug1354478-3-ref.html | 38 + layout/base/tests/bug1354478-3.html | 38 + layout/base/tests/bug1354478-4-ref.html | 38 + layout/base/tests/bug1354478-4.html | 38 + layout/base/tests/bug1354478-5-ref.html | 38 + layout/base/tests/bug1354478-5.html | 38 + layout/base/tests/bug1354478-6-ref.html | 38 + layout/base/tests/bug1354478-6.html | 38 + layout/base/tests/bug1359411-ref.html | 11 + layout/base/tests/bug1359411.html | 12 + layout/base/tests/bug1415416-ref.html | 21 + layout/base/tests/bug1415416.html | 28 + layout/base/tests/bug1423331-1-ref.html | 23 + layout/base/tests/bug1423331-1.html | 28 + layout/base/tests/bug1423331-2-ref.html | 23 + layout/base/tests/bug1423331-2.html | 29 + layout/base/tests/bug1423331-3.html | 26 + layout/base/tests/bug1423331-4.html | 27 + layout/base/tests/bug1448730.html | 103 + layout/base/tests/bug1484094-1-ref.html | 21 + layout/base/tests/bug1484094-1.html | 23 + layout/base/tests/bug1484094-2-ref.html | 21 + layout/base/tests/bug1484094-2.html | 23 + layout/base/tests/bug1496118-ref.html | 25 + layout/base/tests/bug1496118.html | 37 + layout/base/tests/bug1506547-1.html | 29 + layout/base/tests/bug1506547-2.html | 30 + layout/base/tests/bug1506547-3.html | 32 + layout/base/tests/bug1506547-4-ref.html | 21 + layout/base/tests/bug1506547-4.html | 21 + layout/base/tests/bug1506547-5-ref.html | 26 + layout/base/tests/bug1506547-5.html | 27 + layout/base/tests/bug1506547-6.html | 27 + layout/base/tests/bug1510942-1-ref.html | 20 + layout/base/tests/bug1510942-1.html | 21 + layout/base/tests/bug1510942-2-ref.html | 4 + layout/base/tests/bug1510942-2.html | 21 + layout/base/tests/bug1516963-1-ref.html | 16 + layout/base/tests/bug1516963-1.html | 30 + layout/base/tests/bug1516963-2-ref.html | 16 + layout/base/tests/bug1516963-2.html | 30 + layout/base/tests/bug1516963-3-ref.html | 20 + layout/base/tests/bug1516963-3.html | 34 + layout/base/tests/bug1516963-4-ref.html | 20 + layout/base/tests/bug1516963-4.html | 34 + layout/base/tests/bug1516963-5-ref.html | 16 + layout/base/tests/bug1516963-5.html | 30 + layout/base/tests/bug1516963-6-ref.html | 20 + layout/base/tests/bug1516963-6.html | 34 + layout/base/tests/bug1518339-1-ref.html | 16 + layout/base/tests/bug1518339-1.html | 23 + layout/base/tests/bug1518339-2-ref.html | 25 + layout/base/tests/bug1518339-2.html | 23 + layout/base/tests/bug1524266-1-ref.html | 30 + layout/base/tests/bug1524266-1.html | 27 + layout/base/tests/bug1524266-2-ref.html | 24 + layout/base/tests/bug1524266-2.html | 38 + layout/base/tests/bug1524266-3.html | 37 + layout/base/tests/bug1524266-4.html | 30 + layout/base/tests/bug1529492-1-ref.html | 30 + layout/base/tests/bug1529492-1.html | 35 + layout/base/tests/bug1550869-1-ref.html | 19 + layout/base/tests/bug1550869-1a.html | 33 + layout/base/tests/bug1550869-1b.html | 33 + layout/base/tests/bug1550869-1c.html | 33 + layout/base/tests/bug1550869-2-ref.html | 15 + layout/base/tests/bug1550869-2a.html | 30 + layout/base/tests/bug1550869-2b.html | 30 + layout/base/tests/bug1550869-2c.html | 30 + layout/base/tests/bug1550869-2d.html | 30 + layout/base/tests/bug1591282-1-ref.html | 23 + layout/base/tests/bug1591282-1.html | 27 + layout/base/tests/bug1611661-ref.html | 18 + layout/base/tests/bug1611661.html | 23 + layout/base/tests/bug1634543-1-ref.html | 20 + layout/base/tests/bug1634543-1.html | 25 + layout/base/tests/bug1634543-2.html | 24 + layout/base/tests/bug1634543-3.html | 20 + layout/base/tests/bug1634543-4.html | 23 + layout/base/tests/bug1634743-1-ref.html | 23 + layout/base/tests/bug1634743-1.html | 29 + layout/base/tests/bug1637476-1-ref.html | 24 + layout/base/tests/bug1637476-1.html | 24 + layout/base/tests/bug1637476-2-ref.html | 24 + layout/base/tests/bug1637476-2.html | 24 + layout/base/tests/bug1637476-3-ref.html | 24 + layout/base/tests/bug1637476-3.html | 24 + layout/base/tests/bug1663475-1-ref.html | 28 + layout/base/tests/bug1663475-1.html | 32 + layout/base/tests/bug1663475-2-ref.html | 29 + layout/base/tests/bug1663475-2.html | 33 + layout/base/tests/bug1670531-1.html | 27 + layout/base/tests/bug1670531-2.html | 27 + layout/base/tests/bug1670531-3-ref.html | 27 + layout/base/tests/bug1670531-3.html | 27 + layout/base/tests/bug1670531-4.html | 27 + layout/base/tests/bug240933-1-ref.html | 12 + layout/base/tests/bug240933-1.html | 13 + layout/base/tests/bug240933-2.html | 15 + layout/base/tests/bug369950-subframe.xml | 11 + layout/base/tests/bug389321-1-ref.html | 17 + layout/base/tests/bug389321-1.html | 19 + layout/base/tests/bug389321-2-ref.html | 9 + layout/base/tests/bug389321-2.html | 9 + layout/base/tests/bug389321-3-ref.html | 9 + layout/base/tests/bug389321-3.html | 9 + layout/base/tests/bug450930.xhtml | 181 + layout/base/tests/bug482484-ref.html | 18 + layout/base/tests/bug482484.html | 22 + layout/base/tests/bug503399-ref.html | 42 + layout/base/tests/bug503399.html | 41 + layout/base/tests/bug512295-1-ref.html | 30 + layout/base/tests/bug512295-1.html | 36 + layout/base/tests/bug512295-2-ref.html | 30 + layout/base/tests/bug512295-2.html | 36 + layout/base/tests/bug558663.html | 103 + layout/base/tests/bug583889_inner1.html | 64 + layout/base/tests/bug583889_inner2.html | 5 + layout/base/tests/bug585922-ref.html | 21 + layout/base/tests/bug585922.html | 35 + layout/base/tests/bug597519-1-ref.html | 12 + layout/base/tests/bug597519-1.html | 15 + layout/base/tests/bug602141-1-ref.html | 18 + layout/base/tests/bug602141-1.html | 21 + layout/base/tests/bug602141-2-ref.html | 18 + layout/base/tests/bug602141-2.html | 23 + layout/base/tests/bug602141-3-ref.html | 18 + layout/base/tests/bug602141-3.html | 21 + layout/base/tests/bug602141-4-ref.html | 18 + layout/base/tests/bug602141-4.html | 21 + layout/base/tests/bug612271-1.html | 15 + layout/base/tests/bug612271-2.html | 15 + layout/base/tests/bug612271-3.html | 15 + layout/base/tests/bug612271-ref.html | 17 + layout/base/tests/bug613433-1.html | 24 + layout/base/tests/bug613433-2.html | 24 + layout/base/tests/bug613433-3.html | 24 + layout/base/tests/bug613433-ref.html | 21 + layout/base/tests/bug613807-1-ref.html | 6 + layout/base/tests/bug613807-1.html | 90 + layout/base/tests/bug632215-1.html | 29 + layout/base/tests/bug632215-2.html | 28 + layout/base/tests/bug632215-ref.html | 17 + layout/base/tests/bug633044-1-ref.html | 16 + layout/base/tests/bug633044-1.html | 24 + layout/base/tests/bug634406-1-ref.html | 10 + layout/base/tests/bug634406-1.html | 16 + layout/base/tests/bug644428-1-ref.html | 17 + layout/base/tests/bug644428-1.html | 19 + layout/base/tests/bug646382-1-ref.html | 21 + layout/base/tests/bug646382-1.html | 22 + layout/base/tests/bug646382-2-ref.html | 18 + layout/base/tests/bug646382-2.html | 21 + layout/base/tests/bug664087-1-ref.html | 21 + layout/base/tests/bug664087-1.html | 25 + layout/base/tests/bug664087-2-ref.html | 21 + layout/base/tests/bug664087-2.html | 25 + layout/base/tests/bug682712-1-ref.html | 24 + layout/base/tests/bug682712-1.html | 32 + layout/base/tests/bug687297_a.html | 17 + layout/base/tests/bug687297_b.html | 17 + layout/base/tests/bug687297_c.html | 17 + layout/base/tests/bug746993-1-ref.html | 20 + layout/base/tests/bug746993-1.html | 22 + layout/base/tests/bug851445_helper.html | 11 + layout/base/tests/bug923376-ref.html | 12 + layout/base/tests/bug923376.html | 16 + layout/base/tests/bug956530-1-ref.html | 29 + layout/base/tests/bug956530-1.html | 35 + layout/base/tests/bug966992-1-ref.html | 40 + layout/base/tests/bug966992-1.html | 36 + layout/base/tests/bug966992-2-ref.html | 42 + layout/base/tests/bug966992-2.html | 38 + layout/base/tests/bug970964_inner.html | 357 + layout/base/tests/bug970964_inner2.html | 356 + layout/base/tests/bug977003_inner_1.html | 100 + layout/base/tests/bug977003_inner_2.html | 75 + layout/base/tests/bug977003_inner_3.html | 95 + layout/base/tests/bug977003_inner_4.html | 100 + layout/base/tests/bug977003_inner_5.html | 121 + layout/base/tests/bug977003_inner_6.html | 105 + layout/base/tests/bug989012-1-ref.html | 21 + layout/base/tests/bug989012-1.html | 24 + layout/base/tests/bug989012-2-ref.html | 26 + layout/base/tests/bug989012-2.html | 29 + layout/base/tests/bug989012-3-ref.html | 28 + layout/base/tests/bug989012-3.html | 31 + layout/base/tests/chrome/animated.gif | Bin 0 -> 527 bytes layout/base/tests/chrome/blue-32x32.png | Bin 0 -> 110 bytes layout/base/tests/chrome/bug1041200_frame.html | 2 + layout/base/tests/chrome/bug1041200_window.html | 40 + layout/base/tests/chrome/bug1722890.html | 18 + layout/base/tests/chrome/bug1722890_ref.html | 19 + layout/base/tests/chrome/bug1769161_1.html | 17 + layout/base/tests/chrome/bug1769161_1_ref.html | 17 + layout/base/tests/chrome/bug1769161_2.html | 17 + layout/base/tests/chrome/bug1769161_2_ref.html | 17 + layout/base/tests/chrome/bug1769161_3.html | 17 + layout/base/tests/chrome/bug1769161_3_ref.html | 18 + layout/base/tests/chrome/bug1769161_4.html | 17 + layout/base/tests/chrome/bug1769161_4_ref.html | 18 + layout/base/tests/chrome/bug495648.rdf | 214 + layout/base/tests/chrome/bug551434_childframe.html | 4 + layout/base/tests/chrome/chrome.toml | 167 + .../chrome/chrome_content_integration_window.xhtml | 45 + layout/base/tests/chrome/color_adjust.html | 9 + layout/base/tests/chrome/color_adjust_ref.html | 8 + .../tests/chrome/default_background_window.xhtml | 67 + .../chrome/dialog_with_positioning_window.xhtml | 30 + layout/base/tests/chrome/file_bug1018265.xhtml | 50 + layout/base/tests/chrome/file_bug458898.html | 1 + layout/base/tests/chrome/file_bug465448.html | 1 + .../chrome/frame_css_visibility_propagation.html | 1 + layout/base/tests/chrome/green.png | Bin 0 -> 91 bytes layout/base/tests/chrome/markA.ttf | Bin 0 -> 1568 bytes layout/base/tests/chrome/markB.ttf | Bin 0 -> 1568 bytes layout/base/tests/chrome/print_page_size1.html | 31 + layout/base/tests/chrome/print_page_size1_ref.html | 31 + layout/base/tests/chrome/print_page_size2.html | 31 + layout/base/tests/chrome/print_page_size2_ref.html | 34 + layout/base/tests/chrome/print_page_size3.html | 31 + layout/base/tests/chrome/print_page_size3_ref.html | 33 + layout/base/tests/chrome/print_page_size4.html | 31 + layout/base/tests/chrome/print_page_size4_ref.html | 31 + .../tests/chrome/printpreview_bug1713404_ref.html | 27 + .../tests/chrome/printpreview_bug1730091_ref.html | 27 + .../chrome/printpreview_bug396024_helper.xhtml | 96 + .../chrome/printpreview_bug482976_helper.xhtml | 50 + .../chrome/printpreview_downloadable_font.html | 24 + .../printpreview_downloadable_font_in_iframe.html | 25 + ...intpreview_downloadable_font_in_iframe_ref.html | 24 + .../chrome/printpreview_downloadable_font_ref.html | 23 + .../base/tests/chrome/printpreview_font_api.html | 25 + .../tests/chrome/printpreview_font_api_ref.html | 23 + .../chrome/printpreview_font_mozprintcallback.html | 27 + .../printpreview_font_mozprintcallback_ref.html | 28 + layout/base/tests/chrome/printpreview_helper.xhtml | 1721 ++ .../tests/chrome/printpreview_image_select.html | 7 + .../chrome/printpreview_image_select_ref.html | 4 + layout/base/tests/chrome/printpreview_images.html | 25 + .../base/tests/chrome/printpreview_images_ref.html | 15 + .../base/tests/chrome/printpreview_images_sw.html | 46 + layout/base/tests/chrome/printpreview_images_sw.js | 11 + .../tests/chrome/printpreview_images_sw_ref.html | 14 + layout/base/tests/chrome/printpreview_mask.html | 16 + .../chrome/printpreview_mixed_page_size_001.html | 11 + .../chrome/printpreview_mixed_page_size_002.html | 15 + layout/base/tests/chrome/printpreview_pps16.html | 43 + .../base/tests/chrome/printpreview_pps16_ref.html | 49 + layout/base/tests/chrome/printpreview_pps2.html | 15 + .../base/tests/chrome/printpreview_pps2_ref.html | 40 + layout/base/tests/chrome/printpreview_pps4.html | 19 + .../base/tests/chrome/printpreview_pps4_ref.html | 25 + layout/base/tests/chrome/printpreview_pps6.html | 40 + .../base/tests/chrome/printpreview_pps6_ref.html | 90 + layout/base/tests/chrome/printpreview_pps9.html | 29 + .../base/tests/chrome/printpreview_pps9_ref.html | 40 + layout/base/tests/chrome/printpreview_pps_uw2.html | 16 + .../chrome/printpreview_pps_uw2_no_margin_ref.html | 36 + .../tests/chrome/printpreview_pps_uw2_ref.html | 49 + layout/base/tests/chrome/printpreview_pps_uw4.html | 19 + .../tests/chrome/printpreview_pps_uw4_ref.html | 70 + layout/base/tests/chrome/printpreview_pps_uw9.html | 29 + .../tests/chrome/printpreview_pps_uw9_ref.html | 82 + .../base/tests/chrome/printpreview_prettyprint.xml | 1 + .../chrome/printpreview_prettyprint_ref.xhtml | 3 + layout/base/tests/chrome/printpreview_quirks.html | 8 + .../base/tests/chrome/printpreview_quirks_ref.html | 5 + layout/base/tests/chrome/red.png | Bin 0 -> 510 bytes layout/base/tests/chrome/test_bug1018265.xhtml | 37 + layout/base/tests/chrome/test_bug1041200.xhtml | 22 + layout/base/tests/chrome/test_bug396367-1.html | 40 + layout/base/tests/chrome/test_bug396367-2.html | 47 + layout/base/tests/chrome/test_bug420499.xhtml | 126 + layout/base/tests/chrome/test_bug458898.html | 39 + layout/base/tests/chrome/test_bug465448.xhtml | 45 + layout/base/tests/chrome/test_bug514660.xhtml | 35 + layout/base/tests/chrome/test_bug533845.xhtml | 49 + layout/base/tests/chrome/test_bug551434.html | 95 + layout/base/tests/chrome/test_bug708062.html | 43 + layout/base/tests/chrome/test_bug812817.xhtml | 37 + .../chrome/test_chrome_content_integration.xhtml | 24 + .../tests/chrome/test_color_scheme_browser.xhtml | 145 + .../chrome/test_css_visibility_propagation.xhtml | 209 + .../tests/chrome/test_default_background.xhtml | 22 + .../tests/chrome/test_dialog_with_positioning.html | 20 + .../tests/chrome/test_document_adopted_styles.html | 8 + .../chrome/test_document_adopted_styles_ref.html | 6 + .../chrome/test_fixed_bg_scrolling_repaints.html | 40 + .../tests/chrome/test_getClientRectsAndTexts.html | 80 + .../chrome/test_get_printer_basic_attributes.html | 36 + .../tests/chrome/test_get_printer_orientation.html | 50 + .../tests/chrome/test_get_printer_paper_sizes.html | 69 + .../tests/chrome/test_prerendered_transforms.html | 46 + .../chrome/test_printer_default_settings.html | 63 + layout/base/tests/chrome/test_printpreview.xhtml | 16 + .../tests/chrome/test_printpreview_bug396024.xhtml | 21 + .../tests/chrome/test_printpreview_bug482976.xhtml | 21 + .../base/tests/chrome/test_scrolling_repaints.html | 48 + .../chrome/test_shadow_root_adopted_styles.html | 11 + .../test_shadow_root_adopted_styles_ref.html | 11 + .../tests/chrome/test_shared_adopted_styles.html | 19 + .../chrome/test_shared_adopted_styles_ref.html | 16 + layout/base/tests/chrome/test_will_change.html | 140 + .../window_css_visibility_propagation-1.xhtml | 6 + .../window_css_visibility_propagation-2.xhtml | 6 + .../window_css_visibility_propagation-3.html | 3 + .../window_css_visibility_propagation-4.html | 3 + ...into-editing-host-during-blur-of-input-ref.html | 27 + ...ion-into-editing-host-during-blur-of-input.html | 33 + layout/base/tests/file_bug607529-1.html | 12 + layout/base/tests/file_bug607529.html | 51 + layout/base/tests/file_bug842853-frame.html | 6 + layout/base/tests/file_bug842853.html | 17 + layout/base/tests/file_bug842853.sjs | 16 + .../tests/file_dynamic_toolbar_max_height.html | 56 + ...le_getBoxQuads_convertPointRectQuad_frame1.html | 3 + ...le_getBoxQuads_convertPointRectQuad_frame2.html | 1 + layout/base/tests/file_lazyload_telemetry.html | 9 + .../base/tests/file_stylesheet_change_events.html | 10 + layout/base/tests/file_synthmousemove.html | 49 + layout/base/tests/file_zoom_restore_bfcache.html | 92 + layout/base/tests/helper_bug1701027-1.html | 10 + layout/base/tests/helper_bug1701027-2.html | 10 + layout/base/tests/helper_synthmousemove.html | 3 + layout/base/tests/image_rgrg-256x256.png | Bin 0 -> 131 bytes layout/base/tests/input-invalid-ref.html | 7 + .../base/tests/input-maxlength-invalid-change.html | 25 + .../tests/input-maxlength-ui-invalid-change.html | 25 + .../tests/input-maxlength-ui-valid-change.html | 28 + .../tests/input-maxlength-valid-before-change.html | 15 + .../base/tests/input-maxlength-valid-change.html | 28 + .../base/tests/input-minlength-invalid-change.html | 25 + .../tests/input-minlength-ui-invalid-change.html | 25 + .../tests/input-minlength-ui-valid-change.html | 28 + .../tests/input-minlength-valid-before-change.html | 15 + .../base/tests/input-minlength-valid-change.html | 28 + .../base/tests/input-password-RTL-input-ref.html | 23 + layout/base/tests/input-password-RTL-input.html | 31 + layout/base/tests/input-password-remask-ref.html | 20 + layout/base/tests/input-password-remask.html | 23 + .../input-password-unmask-around-emoji-ref.html | 40 + .../tests/input-password-unmask-around-emoji.html | 21 + layout/base/tests/input-password-unmask-ref.html | 33 + layout/base/tests/input-password-unmask.html | 25 + layout/base/tests/input-stoppropagation-ref.html | 16 + layout/base/tests/input-stoppropagation.html | 20 + layout/base/tests/input-ui-valid-ref.html | 6 + layout/base/tests/input-valid-ref.html | 7 + ...rlinePosition-after-Selection-addRange-ref.html | 20 + ...interlinePosition-after-Selection-addRange.html | 21 + layout/base/tests/marionette/manifest.toml | 7 + layout/base/tests/marionette/selection.py | 363 + .../marionette/test_accessiblecaret_cursor_mode.py | 280 + .../test_accessiblecaret_selection_mode.py | 805 + layout/base/tests/mochitest.toml | 594 + .../base/tests/multi-range-script-select-ref.html | 173 + layout/base/tests/multi-range-script-select.html | 185 + layout/base/tests/multi-range-user-select-ref.html | 166 + layout/base/tests/multi-range-user-select.html | 223 + layout/base/tests/partial.png | Bin 0 -> 4000 bytes .../preserve3d_sorting_hit_testing2_iframe.html | 89 + .../preserve3d_sorting_hit_testing_iframe.html | 28 + layout/base/tests/resize_flush_iframe.html | 17 + layout/base/tests/scroll_into_view_in_child.html | 8 + .../tests/scroll_selection_into_view_window.html | 59 + .../scroll_selection_into_view_window_frame.html | 6 + layout/base/tests/selection-utils.js | 167 + layout/base/tests/sendimagenevercomplete.sjs | 29 + layout/base/tests/stylesheet_change_events.css | 1 + .../base/tests/test_accessiblecaret_magnifier.html | 33 + layout/base/tests/test_after_paint_pref.html | 123 + .../base/tests/test_border_radius_hit_testing.html | 106 + layout/base/tests/test_bug1078327.html | 30 + layout/base/tests/test_bug1080360.html | 30 + layout/base/tests/test_bug1080361.html | 31 + layout/base/tests/test_bug1093686.html | 41 + layout/base/tests/test_bug1120705.html | 98 + layout/base/tests/test_bug114649.html | 78 + layout/base/tests/test_bug1153130.html | 30 + layout/base/tests/test_bug1162990.html | 32 + layout/base/tests/test_bug1216483.html | 204 + layout/base/tests/test_bug1226904.html | 44 + layout/base/tests/test_bug1246622.html | 45 + layout/base/tests/test_bug1278021.html | 45 + layout/base/tests/test_bug1448730.html | 30 + layout/base/tests/test_bug1515822.html | 47 + layout/base/tests/test_bug1550869_video.html | 35 + layout/base/tests/test_bug1714640.html | 36 + layout/base/tests/test_bug1756118.html | 87 + layout/base/tests/test_bug332655-1.html | 57 + layout/base/tests/test_bug332655-2.html | 67 + layout/base/tests/test_bug369950.html | 91 + layout/base/tests/test_bug370436.html | 91 + layout/base/tests/test_bug386575.xhtml | 46 + layout/base/tests/test_bug388019.html | 44 + layout/base/tests/test_bug394057.html | 88 + layout/base/tests/test_bug399284.html | 115 + layout/base/tests/test_bug399951.html | 34 + layout/base/tests/test_bug404209.xhtml | 47 + layout/base/tests/test_bug416896.html | 64 + layout/base/tests/test_bug423523.html | 104 + layout/base/tests/test_bug435293-interaction.html | 49 + layout/base/tests/test_bug435293-scale.html | 103 + layout/base/tests/test_bug435293-skew.html | 173 + layout/base/tests/test_bug449781.html | 62 + layout/base/tests/test_bug450930.xhtml | 28 + layout/base/tests/test_bug469170.html | 48 + layout/base/tests/test_bug471126.html | 34 + layout/base/tests/test_bug499538-1.html | 53 + layout/base/tests/test_bug514127.html | 55 + layout/base/tests/test_bug518777.html | 44 + layout/base/tests/test_bug548545.xhtml | 47 + layout/base/tests/test_bug558663.html | 37 + layout/base/tests/test_bug559499.html | 26 + layout/base/tests/test_bug569520.html | 67 + layout/base/tests/test_bug582181-1.html | 60 + layout/base/tests/test_bug582181-2.html | 63 + layout/base/tests/test_bug582771.html | 128 + layout/base/tests/test_bug583889.html | 53 + layout/base/tests/test_bug588174.html | 67 + layout/base/tests/test_bug603550.html | 118 + layout/base/tests/test_bug607529.html | 119 + layout/base/tests/test_bug629838.html | 87 + layout/base/tests/test_bug644768.html | 62 + layout/base/tests/test_bug646757.html | 43 + layout/base/tests/test_bug66619.html | 72 + layout/base/tests/test_bug667512.html | 40 + layout/base/tests/test_bug677878.html | 54 + layout/base/tests/test_bug687297.html | 54 + layout/base/tests/test_bug696020.html | 47 + layout/base/tests/test_bug718809.html | 28 + layout/base/tests/test_bug725426.html | 23 + layout/base/tests/test_bug731777.html | 49 + layout/base/tests/test_bug749186.html | 40 + layout/base/tests/test_bug761572.html | 40 + layout/base/tests/test_bug770106.html | 24 + layout/base/tests/test_bug842853-2.html | 56 + layout/base/tests/test_bug842853.html | 52 + layout/base/tests/test_bug849219.html | 50 + layout/base/tests/test_bug851445.html | 34 + layout/base/tests/test_bug851485.html | 86 + layout/base/tests/test_bug858459.html | 59 + layout/base/tests/test_bug93077-1.html | 31 + layout/base/tests/test_bug93077-2.html | 31 + layout/base/tests/test_bug93077-3.html | 34 + layout/base/tests/test_bug93077-4.html | 34 + layout/base/tests/test_bug93077-5.html | 34 + layout/base/tests/test_bug93077-6.html | 34 + layout/base/tests/test_bug970964.html | 46 + layout/base/tests/test_bug977003.html | 32 + layout/base/tests/test_bug990340.html | 60 + layout/base/tests/test_bug993936.html | 164 + .../test_caret_browsing_around_form_controls.html | 379 + .../tests/test_dynamic_toolbar_max_height.html | 22 + layout/base/tests/test_emulateMedium.html | 165 + layout/base/tests/test_emulate_color_scheme.html | 40 + .../base/tests/test_event_target_iframe_oop.html | 177 + layout/base/tests/test_event_target_radius.html | 422 + .../test_frame_reconstruction_body_table.html | 48 + ...est_frame_reconstruction_body_writing_mode.html | 34 + .../test_frame_reconstruction_for_column_span.html | 77 + ...t_frame_reconstruction_for_pseudo_elements.html | 74 + ...st_frame_reconstruction_for_svg_transforms.html | 46 + .../test_frame_reconstruction_scroll_restore.html | 82 + .../test_getBoxQuads_convertPointRectQuad.html | 717 + .../base/tests/test_getClientRects_emptytext.html | 26 + layout/base/tests/test_mozPaintCount.html | 62 + layout/base/tests/test_partialbg.html | 76 + .../tests/test_preserve3d_sorting_hit_testing.html | 48 + .../test_preserve3d_sorting_hit_testing2.html | 40 + .../tests/test_refreshDriver_hasPendingTick.html | 95 + layout/base/tests/test_reftests_with_caret.html | 465 + layout/base/tests/test_resize_flush.html | 51 + layout/base/tests/test_scroll_event_ordering.html | 63 + .../base/tests/test_scroll_into_view_in_oopif.html | 17 + .../tests/test_scroll_selection_into_view.html | 97 + ...test_scroll_space_no_range_overflow_scroll.html | 67 + layout/base/tests/test_synthmousemove.html | 31 + .../tests/test_transformed_scrolling_repaints.html | 54 + .../test_transformed_scrolling_repaints_2.html | 54 + .../test_transformed_scrolling_repaints_3.html | 24 + .../base/tests/test_visual_viewport_in_oopif.html | 11 + layout/base/tests/test_zoom_restore_bfcache.html | 127 + layout/base/tests/textarea-invalid-ref.html | 7 + .../tests/textarea-maxlength-invalid-change.html | 25 + .../textarea-maxlength-ui-invalid-change.html | 25 + .../tests/textarea-maxlength-ui-valid-change.html | 28 + .../textarea-maxlength-valid-before-change.html | 15 + .../tests/textarea-maxlength-valid-change.html | 28 + .../tests/textarea-minlength-invalid-change.html | 25 + .../textarea-minlength-ui-invalid-change.html | 25 + .../tests/textarea-minlength-ui-valid-change.html | 28 + .../textarea-minlength-valid-before-change.html | 15 + .../tests/textarea-minlength-valid-change.html | 28 + layout/base/tests/textarea-valid-ref.html | 7 + .../transformed_scrolling_repaints_3_window.html | 46 + ...nsformed_scrolling_repaints_3_window_frame.html | 58 + layout/base/tests/visual_viewport_in_child.html | 25 + 1297 files changed, 150106 insertions(+) create mode 100644 layout/base/AccessibleCaret.cpp create mode 100644 layout/base/AccessibleCaret.h create mode 100644 layout/base/AccessibleCaretEventHub.cpp create mode 100644 layout/base/AccessibleCaretEventHub.h create mode 100644 layout/base/AccessibleCaretLogger.h create mode 100644 layout/base/AccessibleCaretManager.cpp create mode 100644 layout/base/AccessibleCaretManager.h create mode 100644 layout/base/ArenaObjectID.h create mode 100644 layout/base/AutoProfilerStyleMarker.h create mode 100644 layout/base/Baseline.cpp create mode 100644 layout/base/Baseline.h create mode 100644 layout/base/CaretAssociationHint.h create mode 100644 layout/base/ContainStyleScopeManager.cpp create mode 100644 layout/base/ContainStyleScopeManager.h create mode 100644 layout/base/DepthOrderedFrameList.cpp create mode 100644 layout/base/DepthOrderedFrameList.h create mode 100644 layout/base/DisplayPortUtils.cpp create mode 100644 layout/base/DisplayPortUtils.h create mode 100644 layout/base/FrameProperties.h create mode 100644 layout/base/GeckoMVMContext.cpp create mode 100644 layout/base/GeckoMVMContext.h create mode 100644 layout/base/GeometryUtils.cpp create mode 100644 layout/base/GeometryUtils.h create mode 100644 layout/base/LayoutConstants.h create mode 100644 layout/base/LayoutLogging.cpp create mode 100644 layout/base/LayoutLogging.h create mode 100644 layout/base/LayoutTelemetryTools.cpp create mode 100644 layout/base/LayoutTelemetryTools.h create mode 100644 layout/base/MVMContext.h create mode 100644 layout/base/MediaEmulationData.h create mode 100644 layout/base/MobileViewportManager.cpp create mode 100644 layout/base/MobileViewportManager.h create mode 100644 layout/base/MotionPathUtils.cpp create mode 100644 layout/base/MotionPathUtils.h create mode 100644 layout/base/OverflowChangedTracker.h create mode 100644 layout/base/PositionedEventTargeting.cpp create mode 100644 layout/base/PositionedEventTargeting.h create mode 100644 layout/base/PresShell.cpp create mode 100644 layout/base/PresShell.h create mode 100644 layout/base/PresShellForwards.h create mode 100644 layout/base/PresShellInlines.h create mode 100644 layout/base/PresState.ipdlh create mode 100644 layout/base/RelativeTo.h create mode 100644 layout/base/RestyleManager.cpp create mode 100644 layout/base/RestyleManager.h create mode 100644 layout/base/ScrollStyles.cpp create mode 100644 layout/base/ScrollStyles.h create mode 100644 layout/base/ScrollTypes.h create mode 100644 layout/base/ShapeUtils.cpp create mode 100644 layout/base/ShapeUtils.h create mode 100644 layout/base/StackArena.cpp create mode 100644 layout/base/StackArena.h create mode 100644 layout/base/StaticPresData.cpp create mode 100644 layout/base/StaticPresData.h create mode 100644 layout/base/SurfaceFromElementResult.h create mode 100644 layout/base/TouchManager.cpp create mode 100644 layout/base/TouchManager.h create mode 100644 layout/base/UnitTransforms.h create mode 100644 layout/base/Units.h create mode 100644 layout/base/ViewportUtils.cpp create mode 100644 layout/base/ViewportUtils.h create mode 100644 layout/base/WordMovementType.h create mode 100644 layout/base/ZoomConstraintsClient.cpp create mode 100644 layout/base/ZoomConstraintsClient.h create mode 100644 layout/base/crashtests/1001237.html create mode 100644 layout/base/crashtests/1009036.html create mode 100644 layout/base/crashtests/1043163-1.html create mode 100644 layout/base/crashtests/1061028.html create mode 100644 layout/base/crashtests/1107508-1.html create mode 100644 layout/base/crashtests/1116104.html create mode 100644 layout/base/crashtests/1127198-1.html create mode 100644 layout/base/crashtests/1140198.html create mode 100644 layout/base/crashtests/1143535.html create mode 100644 layout/base/crashtests/1153716.html create mode 100644 layout/base/crashtests/1156588.html create mode 100644 layout/base/crashtests/1162813.xhtml create mode 100644 layout/base/crashtests/1163583.html create mode 100644 layout/base/crashtests/118931-1.html create mode 100644 layout/base/crashtests/121533-1.html create mode 100644 layout/base/crashtests/123049-1.html create mode 100644 layout/base/crashtests/1234622-1.html create mode 100644 layout/base/crashtests/1235467-1.html create mode 100644 layout/base/crashtests/123946-1.html create mode 100644 layout/base/crashtests/1261351-iframe.html create mode 100644 layout/base/crashtests/1261351.html create mode 100644 layout/base/crashtests/1270797-1.html create mode 100644 layout/base/crashtests/1270797-1.jpg create mode 100644 layout/base/crashtests/1278455-1.html create mode 100644 layout/base/crashtests/1286889.html create mode 100644 layout/base/crashtests/128855-1.html create mode 100644 layout/base/crashtests/1288608.html create mode 100644 layout/base/crashtests/1288946-1.html create mode 100644 layout/base/crashtests/1288946-2a.html create mode 100644 layout/base/crashtests/1288946-2b.html create mode 100644 layout/base/crashtests/1297835.html create mode 100644 layout/base/crashtests/1299736-1.html create mode 100644 layout/base/crashtests/1308793.svg create mode 100644 layout/base/crashtests/1308848-1.html create mode 100644 layout/base/crashtests/1308848-2.html create mode 100644 layout/base/crashtests/133410-1.html create mode 100644 layout/base/crashtests/1338772-1.html create mode 100644 layout/base/crashtests/1340571.html create mode 100644 layout/base/crashtests/1343139-1.html create mode 100644 layout/base/crashtests/1343606.html create mode 100644 layout/base/crashtests/1343937.html create mode 100644 layout/base/crashtests/1352380.html create mode 100644 layout/base/crashtests/1362423-1.html create mode 100644 layout/base/crashtests/1381323.html create mode 100644 layout/base/crashtests/1382534.html create mode 100644 layout/base/crashtests/1388625-1.html create mode 100644 layout/base/crashtests/1390389.html create mode 100644 layout/base/crashtests/1391736.html create mode 100644 layout/base/crashtests/1395591-1.html create mode 100644 layout/base/crashtests/1395715-1.html create mode 100644 layout/base/crashtests/1397398-1.html create mode 100644 layout/base/crashtests/1397398-2.html create mode 100644 layout/base/crashtests/1397398-3.html create mode 100644 layout/base/crashtests/1398500.html create mode 100644 layout/base/crashtests/1400438-1.html create mode 100644 layout/base/crashtests/1400599-1.html create mode 100644 layout/base/crashtests/1401739.html create mode 100644 layout/base/crashtests/1401840.html create mode 100644 layout/base/crashtests/1402476.html create mode 100644 layout/base/crashtests/1404789-2.html create mode 100644 layout/base/crashtests/1406562.html create mode 100644 layout/base/crashtests/1409147.html create mode 100644 layout/base/crashtests/1411138.html create mode 100644 layout/base/crashtests/1414100.html create mode 100644 layout/base/crashtests/1414303.html create mode 100644 layout/base/crashtests/1419762.html create mode 100644 layout/base/crashtests/1419802.html create mode 100644 layout/base/crashtests/1420533.html create mode 100644 layout/base/crashtests/1422908.html create mode 100644 layout/base/crashtests/1425893.html create mode 100644 layout/base/crashtests/1425959.html create mode 100644 layout/base/crashtests/1428353.html create mode 100644 layout/base/crashtests/1428892.html create mode 100644 layout/base/crashtests/1429088.html create mode 100644 layout/base/crashtests/1429961.html create mode 100644 layout/base/crashtests/1429962.html create mode 100644 layout/base/crashtests/1435015.html create mode 100644 layout/base/crashtests/1437155.html create mode 100644 layout/base/crashtests/143862-1a-inner.html create mode 100644 layout/base/crashtests/143862-1a.html create mode 100644 layout/base/crashtests/143862-1b-inner.html create mode 100644 layout/base/crashtests/143862-1b.html create mode 100644 layout/base/crashtests/143862-1c-inner.html create mode 100644 layout/base/crashtests/143862-1c.html create mode 100644 layout/base/crashtests/143862-2.html create mode 100644 layout/base/crashtests/1439016.html create mode 100644 layout/base/crashtests/1442018-1.html create mode 100644 layout/base/crashtests/1442506.html create mode 100644 layout/base/crashtests/1443027-1.html create mode 100644 layout/base/crashtests/1448841-1.html create mode 100644 layout/base/crashtests/1452839.html create mode 100644 layout/base/crashtests/1453196.html create mode 100644 layout/base/crashtests/1453342.html create mode 100644 layout/base/crashtests/1453702.html create mode 100644 layout/base/crashtests/1458121.html create mode 100644 layout/base/crashtests/1461749.html create mode 100644 layout/base/crashtests/1461812.html create mode 100644 layout/base/crashtests/1462412.html create mode 100644 layout/base/crashtests/1463940.html create mode 100644 layout/base/crashtests/1464641.html create mode 100644 layout/base/crashtests/1464737.html create mode 100644 layout/base/crashtests/1466638.html create mode 100644 layout/base/crashtests/1467519.html create mode 100644 layout/base/crashtests/1467688.html create mode 100644 layout/base/crashtests/1467964.html create mode 100644 layout/base/crashtests/1469354.html create mode 100644 layout/base/crashtests/1470499.html create mode 100644 layout/base/crashtests/1472020.html create mode 100644 layout/base/crashtests/1472027.html create mode 100644 layout/base/crashtests/147320-1.html create mode 100644 layout/base/crashtests/1477847.html create mode 100644 layout/base/crashtests/148245-1.html create mode 100644 layout/base/crashtests/1486521.html create mode 100644 layout/base/crashtests/1489149.html create mode 100644 layout/base/crashtests/1490037.html create mode 100644 layout/base/crashtests/149014-1.html create mode 100644 layout/base/crashtests/1494030.html create mode 100644 layout/base/crashtests/1494332.html create mode 100644 layout/base/crashtests/150431-1.html create mode 100644 layout/base/crashtests/1505420.html create mode 100644 layout/base/crashtests/1506163.html create mode 100644 layout/base/crashtests/1506204.html create mode 100644 layout/base/crashtests/1506314.html create mode 100644 layout/base/crashtests/1507244.html create mode 100644 layout/base/crashtests/1510080.html create mode 100644 layout/base/crashtests/1510485.html create mode 100644 layout/base/crashtests/1511442.html create mode 100644 layout/base/crashtests/1511535.html create mode 100644 layout/base/crashtests/1511563.html create mode 100644 layout/base/crashtests/1516286-empty-mask.html create mode 100644 layout/base/crashtests/1524382.html create mode 100644 layout/base/crashtests/1524411.html create mode 100644 layout/base/crashtests/1533885.html create mode 100644 layout/base/crashtests/1534146.html create mode 100644 layout/base/crashtests/1535945.html create mode 100644 layout/base/crashtests/1539017.html create mode 100644 layout/base/crashtests/1539303-iframe.html create mode 100644 layout/base/crashtests/1539303.html create mode 100644 layout/base/crashtests/1541679.html create mode 100644 layout/base/crashtests/1547261.html create mode 100644 layout/base/crashtests/1547391.html create mode 100644 layout/base/crashtests/1548057.html create mode 100644 layout/base/crashtests/1549867.html create mode 100644 layout/base/crashtests/1553874.html create mode 100644 layout/base/crashtests/1560328.html create mode 100644 layout/base/crashtests/1566672.html create mode 100644 layout/base/crashtests/1574101-1.html create mode 100644 layout/base/crashtests/1574101-2.html create mode 100644 layout/base/crashtests/1575908-1.html create mode 100644 layout/base/crashtests/1576972-1.html create mode 100644 layout/base/crashtests/1578844-1.html create mode 100644 layout/base/crashtests/1578844-2.html create mode 100644 layout/base/crashtests/1579953-1.html create mode 100644 layout/base/crashtests/1580576.html create mode 100644 layout/base/crashtests/1586600.html create mode 100644 layout/base/crashtests/1599518.html create mode 100644 layout/base/crashtests/1599532.html create mode 100644 layout/base/crashtests/1606492.html create mode 100644 layout/base/crashtests/1654315.html create mode 100644 layout/base/crashtests/1676301-1.html create mode 100644 layout/base/crashtests/1685146.html create mode 100644 layout/base/crashtests/1689371.html create mode 100644 layout/base/crashtests/1689912.html create mode 100644 layout/base/crashtests/1690163.html create mode 100644 layout/base/crashtests/1723200.html create mode 100644 layout/base/crashtests/1729578.html create mode 100644 layout/base/crashtests/1729581.html create mode 100644 layout/base/crashtests/1734007.html create mode 100644 layout/base/crashtests/1745860.html create mode 100644 layout/base/crashtests/1746989.html create mode 100644 layout/base/crashtests/1747277-1.html create mode 100644 layout/base/crashtests/1752649.html create mode 100644 layout/base/crashtests/1753779.html create mode 100644 layout/base/crashtests/1755790.html create mode 100644 layout/base/crashtests/176915-1.html create mode 100644 layout/base/crashtests/1771503.html create mode 100644 layout/base/crashtests/1789934.html create mode 100644 layout/base/crashtests/1791883.html create mode 100644 layout/base/crashtests/1797995.html create mode 100644 layout/base/crashtests/1818036.html create mode 100644 layout/base/crashtests/1819239.html create mode 100644 layout/base/crashtests/1821469.html create mode 100644 layout/base/crashtests/1849898-1.html create mode 100644 layout/base/crashtests/191272-1.html create mode 100644 layout/base/crashtests/199696-1.html create mode 100644 layout/base/crashtests/217903-1.html create mode 100644 layout/base/crashtests/223064-1.html create mode 100644 layout/base/crashtests/234851-1.html create mode 100644 layout/base/crashtests/234851-2.html create mode 100644 layout/base/crashtests/241300-1.html create mode 100644 layout/base/crashtests/243159-1.html create mode 100644 layout/base/crashtests/243159-2.xhtml create mode 100644 layout/base/crashtests/243519-1.html create mode 100644 layout/base/crashtests/244490-1.html create mode 100644 layout/base/crashtests/254367-1.html create mode 100644 layout/base/crashtests/263359-1.html create mode 100644 layout/base/crashtests/265027-1.html create mode 100644 layout/base/crashtests/265736-1.html create mode 100644 layout/base/crashtests/265736-2.html create mode 100644 layout/base/crashtests/265899-1.html create mode 100644 layout/base/crashtests/265973-1.html create mode 100644 layout/base/crashtests/265986-1.html create mode 100644 layout/base/crashtests/265999-1.html create mode 100644 layout/base/crashtests/266222-1.html create mode 100644 layout/base/crashtests/266360-1.html create mode 100644 layout/base/crashtests/266445-1.html create mode 100644 layout/base/crashtests/266445-2.html create mode 100644 layout/base/crashtests/268157-1.html create mode 100644 layout/base/crashtests/269566-1.html create mode 100644 layout/base/crashtests/272647-1.html create mode 100644 layout/base/crashtests/275746-1.html create mode 100644 layout/base/crashtests/276053-1.html create mode 100644 layout/base/crashtests/280708-1.html create mode 100644 layout/base/crashtests/280708-2.html create mode 100644 layout/base/crashtests/281333-1.html create mode 100644 layout/base/crashtests/285212-1.html create mode 100644 layout/base/crashtests/286813-1.html create mode 100644 layout/base/crashtests/306940-1.html create mode 100644 layout/base/crashtests/310267-1.xml create mode 100644 layout/base/crashtests/310638-1.svg create mode 100644 layout/base/crashtests/310638-2.html create mode 100644 layout/base/crashtests/311661-1.xhtml create mode 100644 layout/base/crashtests/311661-2.xhtml create mode 100644 layout/base/crashtests/313086-1.xml create mode 100644 layout/base/crashtests/317285-1.html create mode 100644 layout/base/crashtests/317934-1-inner.html create mode 100644 layout/base/crashtests/317934-1.html create mode 100644 layout/base/crashtests/320459-1.html create mode 100644 layout/base/crashtests/321058-1.xhtml create mode 100644 layout/base/crashtests/321058-2.xhtml create mode 100644 layout/base/crashtests/321077-1.xhtml create mode 100644 layout/base/crashtests/321077-2.xhtml create mode 100644 layout/base/crashtests/322436-1.html create mode 100644 layout/base/crashtests/322678.html create mode 100644 layout/base/crashtests/325024.html create mode 100644 layout/base/crashtests/325218.xhtml create mode 100644 layout/base/crashtests/325967-1.html create mode 100644 layout/base/crashtests/325984-1.xhtml create mode 100644 layout/base/crashtests/325984-2.html create mode 100644 layout/base/crashtests/328944-1.xhtml create mode 100644 layout/base/crashtests/329900-1.html create mode 100644 layout/base/crashtests/330015-1.html create mode 100644 layout/base/crashtests/331679-1.xhtml create mode 100644 layout/base/crashtests/331679-2.xml create mode 100644 layout/base/crashtests/331679-3.xml create mode 100644 layout/base/crashtests/331883-1-inner.html create mode 100644 layout/base/crashtests/331883-1.html create mode 100644 layout/base/crashtests/335140-1.html create mode 100644 layout/base/crashtests/336291-1.html create mode 100644 layout/base/crashtests/336999-1.xhtml create mode 100644 layout/base/crashtests/337066-1.xhtml create mode 100644 layout/base/crashtests/337268-1.html create mode 100644 layout/base/crashtests/337419-1.html create mode 100644 layout/base/crashtests/337476-1.xhtml create mode 100644 layout/base/crashtests/338703-1.html create mode 100644 layout/base/crashtests/339651-1.html create mode 100644 layout/base/crashtests/340093-1.xhtml create mode 100644 layout/base/crashtests/341382-1.html create mode 100644 layout/base/crashtests/341382-2.html create mode 100644 layout/base/crashtests/341858-1.html create mode 100644 layout/base/crashtests/342145-1.xhtml create mode 100644 layout/base/crashtests/343293-1.xhtml create mode 100644 layout/base/crashtests/343293-2.xhtml create mode 100644 layout/base/crashtests/343540-1.html create mode 100644 layout/base/crashtests/344057-1.xhtml create mode 100644 layout/base/crashtests/344064-1-inner.xhtml create mode 100644 layout/base/crashtests/344064-1.html create mode 100644 layout/base/crashtests/344300-1-inner.xhtml create mode 100644 layout/base/crashtests/344300-1.html create mode 100644 layout/base/crashtests/344340-1.xhtml create mode 100644 layout/base/crashtests/347898-1.html create mode 100644 layout/base/crashtests/348126-1-inner.html create mode 100644 layout/base/crashtests/348126-1.gif create mode 100644 layout/base/crashtests/348126-1.html create mode 100644 layout/base/crashtests/348688-1.html create mode 100644 layout/base/crashtests/348708-1.xhtml create mode 100644 layout/base/crashtests/348729-1-inner.html create mode 100644 layout/base/crashtests/348729-1.html create mode 100644 layout/base/crashtests/349095-1.xhtml create mode 100644 layout/base/crashtests/350267-1.html create mode 100644 layout/base/crashtests/354133-1-inner.xhtml create mode 100644 layout/base/crashtests/354133-1.html create mode 100644 layout/base/crashtests/354766-1.xhtml create mode 100644 layout/base/crashtests/355989-1.xhtml create mode 100644 layout/base/crashtests/355993-1.xhtml create mode 100644 layout/base/crashtests/356325-1.xhtml create mode 100644 layout/base/crashtests/358729-1.xhtml create mode 100644 layout/base/crashtests/360339-1.xhtml create mode 100644 layout/base/crashtests/360339-2.xhtml create mode 100644 layout/base/crashtests/363729-1.html create mode 100644 layout/base/crashtests/363729-2.html create mode 100644 layout/base/crashtests/363729-3.html create mode 100644 layout/base/crashtests/365909-1.xhtml create mode 100644 layout/base/crashtests/365909-2.xhtml create mode 100644 layout/base/crashtests/366128-1.xhtml create mode 100644 layout/base/crashtests/366271-1-frame.svg create mode 100644 layout/base/crashtests/366271-1.html create mode 100644 layout/base/crashtests/366967-1.html create mode 100644 layout/base/crashtests/367015-1.html create mode 100644 layout/base/crashtests/367243-1.html create mode 100644 layout/base/crashtests/369176-1.html create mode 100644 layout/base/crashtests/369547-1.html create mode 100644 layout/base/crashtests/369547-2.html create mode 100644 layout/base/crashtests/369945-1.xhtml create mode 100644 layout/base/crashtests/371681-1.xhtml create mode 100644 layout/base/crashtests/372237-1.html create mode 100644 layout/base/crashtests/372550-1.html create mode 100644 layout/base/crashtests/373628-1.html create mode 100644 layout/base/crashtests/373628.html create mode 100644 layout/base/crashtests/374297-1.html create mode 100644 layout/base/crashtests/374297-2.html create mode 100644 layout/base/crashtests/378325-1.html create mode 100644 layout/base/crashtests/378682.html create mode 100644 layout/base/crashtests/379419-1.xhtml create mode 100644 layout/base/crashtests/379799-1.html create mode 100644 layout/base/crashtests/380096-1.html create mode 100644 layout/base/crashtests/382204-1.html create mode 100644 layout/base/crashtests/383129-1-inner.xhtml create mode 100644 layout/base/crashtests/383129-1.html create mode 100644 layout/base/crashtests/384344-1-inner.html create mode 100644 layout/base/crashtests/384344-1.html create mode 100644 layout/base/crashtests/384392-1.xhtml create mode 100644 layout/base/crashtests/384392-2.svg create mode 100644 layout/base/crashtests/384649-1.xhtml create mode 100644 layout/base/crashtests/385354.html create mode 100644 layout/base/crashtests/385866-1.xhtml create mode 100644 layout/base/crashtests/385880-1.xhtml create mode 100644 layout/base/crashtests/386266-1.html create mode 100644 layout/base/crashtests/386476.html create mode 100644 layout/base/crashtests/387195-1.html create mode 100644 layout/base/crashtests/387195-2.xhtml create mode 100644 layout/base/crashtests/388715-1.html create mode 100644 layout/base/crashtests/390976-1.html create mode 100644 layout/base/crashtests/393661-1.html create mode 100644 layout/base/crashtests/393801-1-inner.html create mode 100644 layout/base/crashtests/393801-1.html create mode 100644 layout/base/crashtests/394150-1.xhtml create mode 100644 layout/base/crashtests/397011-1.xhtml create mode 100644 layout/base/crashtests/398510-1.xhtml create mode 100644 layout/base/crashtests/398733-1.html create mode 100644 layout/base/crashtests/398733-2.html create mode 100644 layout/base/crashtests/399132-1.xhtml create mode 100644 layout/base/crashtests/399219-1.xhtml create mode 100644 layout/base/crashtests/399365-1.html create mode 100644 layout/base/crashtests/399676-1.xhtml create mode 100644 layout/base/crashtests/399687-1.html create mode 100644 layout/base/crashtests/399940-1.xhtml create mode 100644 layout/base/crashtests/399951-1.html create mode 100644 layout/base/crashtests/399994-1.html create mode 100644 layout/base/crashtests/400445-1.xhtml create mode 100644 layout/base/crashtests/400904-1.xhtml create mode 100644 layout/base/crashtests/401734-1.html create mode 100644 layout/base/crashtests/401734-2.html create mode 100644 layout/base/crashtests/403048.html create mode 100644 layout/base/crashtests/403175-1.html create mode 100644 layout/base/crashtests/403245-1.html create mode 100644 layout/base/crashtests/403454.html create mode 100644 layout/base/crashtests/403569-1.xhtml create mode 100644 layout/base/crashtests/403569-2.xhtml create mode 100644 layout/base/crashtests/403569-3.xhtml create mode 100644 layout/base/crashtests/404491-1.html create mode 100644 layout/base/crashtests/404721-1.xhtml create mode 100644 layout/base/crashtests/404721-2.xhtml create mode 100644 layout/base/crashtests/405049-1.xhtml create mode 100644 layout/base/crashtests/406675-1.html create mode 100644 layout/base/crashtests/408292.html create mode 100644 layout/base/crashtests/408299.html create mode 100644 layout/base/crashtests/408450-1.xhtml create mode 100644 layout/base/crashtests/409461-1.xhtml create mode 100644 layout/base/crashtests/410967.html create mode 100644 layout/base/crashtests/411870-1.html create mode 100644 layout/base/crashtests/412651-1-frame.xhtml create mode 100644 layout/base/crashtests/412651-1.html create mode 100644 layout/base/crashtests/413587-1.svg create mode 100644 layout/base/crashtests/415503.xhtml create mode 100644 layout/base/crashtests/416107.xhtml create mode 100644 layout/base/crashtests/419985.html create mode 100644 layout/base/crashtests/420031-1.html create mode 100644 layout/base/crashtests/420213-1.html create mode 100644 layout/base/crashtests/420219-1.html create mode 100644 layout/base/crashtests/420651-1.xhtml create mode 100644 layout/base/crashtests/421203-1.xhtml create mode 100644 layout/base/crashtests/421432.html create mode 100644 layout/base/crashtests/422276.html create mode 100644 layout/base/crashtests/423107-1.xhtml create mode 100644 layout/base/crashtests/425981-1.html create mode 100644 layout/base/crashtests/428138-1.html create mode 100644 layout/base/crashtests/428448-1.html create mode 100644 layout/base/crashtests/429088-1.html create mode 100644 layout/base/crashtests/429088-2.html create mode 100644 layout/base/crashtests/429865-1.html create mode 100644 layout/base/crashtests/429881.html create mode 100644 layout/base/crashtests/430569-1.html create mode 100644 layout/base/crashtests/430569-2.html create mode 100644 layout/base/crashtests/432752-1.svg create mode 100644 layout/base/crashtests/433450-1.html create mode 100644 layout/base/crashtests/436982-1.html create mode 100644 layout/base/crashtests/437142-1.html create mode 100644 layout/base/crashtests/439258-1.html create mode 100644 layout/base/crashtests/439343.html create mode 100644 layout/base/crashtests/444863-1.html create mode 100644 layout/base/crashtests/444925-1.xhtml create mode 100644 layout/base/crashtests/444967-1.html create mode 100644 layout/base/crashtests/446328-iframe.html create mode 100644 layout/base/crashtests/446328-top.html create mode 100644 layout/base/crashtests/446328.gif create mode 100644 layout/base/crashtests/446328.html create mode 100644 layout/base/crashtests/448488-1.html create mode 100644 layout/base/crashtests/448543-1.html create mode 100644 layout/base/crashtests/448543-2.html create mode 100644 layout/base/crashtests/448543-3.html create mode 100644 layout/base/crashtests/450319-1.xhtml create mode 100644 layout/base/crashtests/453894-1.xhtml create mode 100644 layout/base/crashtests/454751-1.xhtml create mode 100644 layout/base/crashtests/455063-1.html create mode 100644 layout/base/crashtests/455063-2.html create mode 100644 layout/base/crashtests/455063-3.html create mode 100644 layout/base/crashtests/455171-4.html create mode 100644 layout/base/crashtests/455623-1.html create mode 100644 layout/base/crashtests/457362-1.xhtml create mode 100644 layout/base/crashtests/457514.html create mode 100644 layout/base/crashtests/460389-1.html create mode 100644 layout/base/crashtests/46043-1.html create mode 100644 layout/base/crashtests/462392.html create mode 100644 layout/base/crashtests/466763-1.html create mode 100644 layout/base/crashtests/467881-1.html create mode 100644 layout/base/crashtests/468491-1.html create mode 100644 layout/base/crashtests/468555-1.xhtml create mode 100644 layout/base/crashtests/468563-1.html create mode 100644 layout/base/crashtests/468578-1.xhtml create mode 100644 layout/base/crashtests/468645-3.xhtml create mode 100644 layout/base/crashtests/469861-1.xhtml create mode 100644 layout/base/crashtests/469861-2.xhtml create mode 100644 layout/base/crashtests/470851-1.xhtml create mode 100644 layout/base/crashtests/473042.xhtml create mode 100644 layout/base/crashtests/474075.html create mode 100644 layout/base/crashtests/477333-1.xhtml create mode 100644 layout/base/crashtests/477731-1.html create mode 100644 layout/base/crashtests/47843-1.html create mode 100644 layout/base/crashtests/479114-1.html create mode 100644 layout/base/crashtests/479360-1.xhtml create mode 100644 layout/base/crashtests/480686-1.html create mode 100644 layout/base/crashtests/481806-1.html create mode 100644 layout/base/crashtests/483604-1.xhtml create mode 100644 layout/base/crashtests/485501-1.html create mode 100644 layout/base/crashtests/488390-1.xhtml create mode 100644 layout/base/crashtests/489691.html create mode 100644 layout/base/crashtests/490376-1.xhtml create mode 100644 layout/base/crashtests/490559-1.html create mode 100644 layout/base/crashtests/490747.html create mode 100644 layout/base/crashtests/49122-1.html create mode 100644 layout/base/crashtests/491547-1.xhtml create mode 100644 layout/base/crashtests/491547-2.xhtml create mode 100644 layout/base/crashtests/492014.xhtml create mode 100644 layout/base/crashtests/492112-1.xhtml create mode 100644 layout/base/crashtests/492163-1.xhtml create mode 100644 layout/base/crashtests/495350-1.html create mode 100644 layout/base/crashtests/496011-1.xhtml create mode 100644 layout/base/crashtests/499741-1.xhtml create mode 100644 layout/base/crashtests/499841-1.xhtml create mode 100644 layout/base/crashtests/499858-1.xhtml create mode 100644 layout/base/crashtests/500467-1.html create mode 100644 layout/base/crashtests/501878-1.html create mode 100644 layout/base/crashtests/50257-1.html create mode 100644 layout/base/crashtests/503936-1.html create mode 100644 layout/base/crashtests/50395-1.html create mode 100644 layout/base/crashtests/507119.html create mode 100644 layout/base/crashtests/522374-1.html create mode 100644 layout/base/crashtests/522374-2.html create mode 100644 layout/base/crashtests/526378-1.xhtml create mode 100644 layout/base/crashtests/534367-1.xhtml create mode 100644 layout/base/crashtests/534368-1.xhtml create mode 100644 layout/base/crashtests/534768-1.html create mode 100644 layout/base/crashtests/534768-2.html create mode 100644 layout/base/crashtests/535721-1.xhtml create mode 100644 layout/base/crashtests/535911-1.xhtml create mode 100644 layout/base/crashtests/536720.xhtml create mode 100644 layout/base/crashtests/537562-1.xhtml create mode 100644 layout/base/crashtests/537624-1.html create mode 100644 layout/base/crashtests/537631-1.html create mode 100644 layout/base/crashtests/538082-1.xhtml create mode 100644 layout/base/crashtests/538207-1.xhtml create mode 100644 layout/base/crashtests/538210-1.html create mode 100644 layout/base/crashtests/538267-1.html create mode 100644 layout/base/crashtests/540760.xhtml create mode 100644 layout/base/crashtests/540771-1.xhtml create mode 100644 layout/base/crashtests/541869-1.xhtml create mode 100644 layout/base/crashtests/541869-2.html create mode 100644 layout/base/crashtests/543648-1.html create mode 100644 layout/base/crashtests/560447-1.html create mode 100644 layout/base/crashtests/564063-1.html create mode 100644 layout/base/crashtests/56746-1.html create mode 100644 layout/base/crashtests/569018-1.html create mode 100644 layout/base/crashtests/572003.xhtml create mode 100644 layout/base/crashtests/572582-1.xhtml create mode 100644 layout/base/crashtests/576649-1.html create mode 100644 layout/base/crashtests/579655.html create mode 100644 layout/base/crashtests/580129-1.html create mode 100644 layout/base/crashtests/580494-1.html create mode 100644 layout/base/crashtests/580834-1.xhtml create mode 100644 layout/base/crashtests/589787.html create mode 100644 layout/base/crashtests/591075-1.html create mode 100644 layout/base/crashtests/591998-1.html create mode 100644 layout/base/crashtests/595039-1.html create mode 100644 layout/base/crashtests/597924-1.html create mode 100644 layout/base/crashtests/606432-1.html create mode 100644 layout/base/crashtests/609821-1.xhtml create mode 100644 layout/base/crashtests/615146-1.html create mode 100644 layout/base/crashtests/615781-1.xhtml create mode 100644 layout/base/crashtests/616495-single-side-composite-color-border.html create mode 100644 layout/base/crashtests/629035-1.html create mode 100644 layout/base/crashtests/629908-1.html create mode 100644 layout/base/crashtests/635329.html create mode 100644 layout/base/crashtests/636229-1.html create mode 100644 layout/base/crashtests/640272-empty.html create mode 100644 layout/base/crashtests/640272-ref.html create mode 100644 layout/base/crashtests/640272.html create mode 100644 layout/base/crashtests/645193.html create mode 100644 layout/base/crashtests/645572-1.html create mode 100644 layout/base/crashtests/650475.xhtml create mode 100644 layout/base/crashtests/650489.xhtml create mode 100644 layout/base/crashtests/651342-1.html create mode 100644 layout/base/crashtests/653133-1.html create mode 100644 layout/base/crashtests/663295.html create mode 100644 layout/base/crashtests/663662-1.html create mode 100644 layout/base/crashtests/663662-2.html create mode 100644 layout/base/crashtests/665837.html create mode 100644 layout/base/crashtests/668579.html create mode 100644 layout/base/crashtests/668941.xhtml create mode 100644 layout/base/crashtests/670226.html create mode 100644 layout/base/crashtests/675246-1.xhtml create mode 100644 layout/base/crashtests/690247-1.html create mode 100644 layout/base/crashtests/690619-1.html create mode 100644 layout/base/crashtests/691118-1.html create mode 100644 layout/base/crashtests/695861.html create mode 100644 layout/base/crashtests/695964-1.svg create mode 100644 layout/base/crashtests/698335.html create mode 100644 layout/base/crashtests/699353-1.html create mode 100644 layout/base/crashtests/701504.html create mode 100644 layout/base/crashtests/707098.html create mode 100644 layout/base/crashtests/709536-1.xhtml create mode 100644 layout/base/crashtests/722137.html create mode 100644 layout/base/crashtests/725535.html create mode 100644 layout/base/crashtests/727601.html create mode 100644 layout/base/crashtests/735943.html create mode 100644 layout/base/crashtests/736389-1.xhtml create mode 100644 layout/base/crashtests/736924-1.html create mode 100644 layout/base/crashtests/749816-1.html create mode 100644 layout/base/crashtests/763223-1.html create mode 100644 layout/base/crashtests/763702.xhtml create mode 100644 layout/base/crashtests/767593-1.html create mode 100644 layout/base/crashtests/767593-2.html create mode 100644 layout/base/crashtests/770381-1.html create mode 100644 layout/base/crashtests/772306.html create mode 100644 layout/base/crashtests/788360.html create mode 100644 layout/base/crashtests/793848.html create mode 100644 layout/base/crashtests/795646.html create mode 100644 layout/base/crashtests/802902.html create mode 100644 layout/base/crashtests/806056-1.html create mode 100644 layout/base/crashtests/806056-2.html create mode 100644 layout/base/crashtests/812665.html create mode 100644 layout/base/crashtests/813372-1.html create mode 100644 layout/base/crashtests/817219-iframe.html create mode 100644 layout/base/crashtests/817219.html create mode 100644 layout/base/crashtests/818454.html create mode 100644 layout/base/crashtests/822865.html create mode 100644 layout/base/crashtests/824300.html create mode 100644 layout/base/crashtests/824862.html create mode 100644 layout/base/crashtests/826163.html create mode 100644 layout/base/crashtests/827192.html create mode 100644 layout/base/crashtests/830138-1.html create mode 100644 layout/base/crashtests/830192-1.html create mode 100644 layout/base/crashtests/830299-1.html create mode 100644 layout/base/crashtests/833604-1.html create mode 100644 layout/base/crashtests/835056.html create mode 100644 layout/base/crashtests/836990-1.html create mode 100644 layout/base/crashtests/840480.html create mode 100644 layout/base/crashtests/842114.html create mode 100644 layout/base/crashtests/847242.html create mode 100644 layout/base/crashtests/852293.html create mode 100644 layout/base/crashtests/859526-1.html create mode 100644 layout/base/crashtests/859630-1.html create mode 100644 layout/base/crashtests/860579-1.html create mode 100644 layout/base/crashtests/866588.html create mode 100644 layout/base/crashtests/876092.html create mode 100644 layout/base/crashtests/876221.html create mode 100644 layout/base/crashtests/89101-1.html create mode 100644 layout/base/crashtests/89358-1.html create mode 100644 layout/base/crashtests/897852.html create mode 100644 layout/base/crashtests/898913.html create mode 100644 layout/base/crashtests/90205-1.html create mode 100644 layout/base/crashtests/926728.html create mode 100644 layout/base/crashtests/930381.html create mode 100644 layout/base/crashtests/931450.html create mode 100644 layout/base/crashtests/931460-1.html create mode 100644 layout/base/crashtests/931464.html create mode 100644 layout/base/crashtests/935765-1.html create mode 100644 layout/base/crashtests/936988-1.html create mode 100644 layout/base/crashtests/942690.html create mode 100644 layout/base/crashtests/973390-1.html create mode 100644 layout/base/crashtests/989994-1.html create mode 100644 layout/base/crashtests/99776-1.html create mode 100644 layout/base/crashtests/crashtests.list create mode 100644 layout/base/gtest/TestAccessibleCaretEventHub.cpp create mode 100644 layout/base/gtest/TestAccessibleCaretManager.cpp create mode 100644 layout/base/gtest/moz.build create mode 100644 layout/base/metrics.yaml create mode 100644 layout/base/moz.build create mode 100644 layout/base/nsAutoLayoutPhase.cpp create mode 100644 layout/base/nsAutoLayoutPhase.h create mode 100644 layout/base/nsBidiPresUtils.cpp create mode 100644 layout/base/nsBidiPresUtils.h create mode 100644 layout/base/nsCSSColorUtils.cpp create mode 100644 layout/base/nsCSSColorUtils.h create mode 100644 layout/base/nsCSSFrameConstructor.cpp create mode 100644 layout/base/nsCSSFrameConstructor.h create mode 100644 layout/base/nsCaret.cpp create mode 100644 layout/base/nsCaret.h create mode 100644 layout/base/nsChangeHint.h create mode 100644 layout/base/nsCompatibility.h create mode 100644 layout/base/nsCounterManager.cpp create mode 100644 layout/base/nsCounterManager.h create mode 100644 layout/base/nsDocumentViewer.cpp create mode 100644 layout/base/nsFrameManager.cpp create mode 100644 layout/base/nsFrameManager.h create mode 100644 layout/base/nsFrameTraversal.cpp create mode 100644 layout/base/nsFrameTraversal.h create mode 100644 layout/base/nsGenConList.cpp create mode 100644 layout/base/nsGenConList.h create mode 100644 layout/base/nsIDocumentViewerPrint.h create mode 100644 layout/base/nsILayoutHistoryState.idl create mode 100644 layout/base/nsIPercentBSizeObserver.h create mode 100644 layout/base/nsIPreloadedStyleSheet.idl create mode 100644 layout/base/nsIReflowCallback.h create mode 100644 layout/base/nsIStyleSheetService.idl create mode 100644 layout/base/nsLayoutDebugger.cpp create mode 100644 layout/base/nsLayoutHistoryState.cpp create mode 100644 layout/base/nsLayoutUtils.cpp create mode 100644 layout/base/nsLayoutUtils.h create mode 100644 layout/base/nsPresArena.cpp create mode 100644 layout/base/nsPresArena.h create mode 100644 layout/base/nsPresArenaObjectList.h create mode 100644 layout/base/nsPresContext.cpp create mode 100644 layout/base/nsPresContext.h create mode 100644 layout/base/nsPresContextInlines.h create mode 100644 layout/base/nsQuoteList.cpp create mode 100644 layout/base/nsQuoteList.h create mode 100644 layout/base/nsRefreshDriver.cpp create mode 100644 layout/base/nsRefreshDriver.h create mode 100644 layout/base/nsRefreshObservers.cpp create mode 100644 layout/base/nsRefreshObservers.h create mode 100644 layout/base/nsStyleChangeList.cpp create mode 100644 layout/base/nsStyleChangeList.h create mode 100644 layout/base/nsStyleSheetService.cpp create mode 100644 layout/base/nsStyleSheetService.h create mode 100644 layout/base/tests/Ahem.ttf create mode 100644 layout/base/tests/accessiblecaret_magnifier.html create mode 100644 layout/base/tests/border_radius_hit_testing_iframe.html create mode 100644 layout/base/tests/browser.toml create mode 100644 layout/base/tests/browser_bug1701027-1.js create mode 100644 layout/base/tests/browser_bug1701027-2.js create mode 100644 layout/base/tests/browser_bug1757410.js create mode 100644 layout/base/tests/browser_bug1787079.js create mode 100644 layout/base/tests/browser_bug1791083.js create mode 100644 layout/base/tests/browser_bug617076.js create mode 100644 layout/base/tests/browser_css_registered_property.js create mode 100644 layout/base/tests/browser_disableDialogs_onbeforeunload.js create mode 100644 layout/base/tests/browser_onbeforeunload_only_after_interaction.js create mode 100644 layout/base/tests/browser_onbeforeunload_only_after_interaction_in_frame.js create mode 100644 layout/base/tests/browser_scroll_into_view_in_out_of_process_iframe.js create mode 100644 layout/base/tests/browser_select_popup_position_in_out_of_process_iframe.js create mode 100644 layout/base/tests/browser_stylesheet_change_events.js create mode 100644 layout/base/tests/browser_visual_viewport_iframe.js create mode 100644 layout/base/tests/bug1007065-1-ref.html create mode 100644 layout/base/tests/bug1007065-1.html create mode 100644 layout/base/tests/bug1007067-1-ref.html create mode 100644 layout/base/tests/bug1007067-1.html create mode 100644 layout/base/tests/bug1061468-ref.html create mode 100644 layout/base/tests/bug1061468.html create mode 100644 layout/base/tests/bug106855-1-ref.html create mode 100644 layout/base/tests/bug106855-1.html create mode 100644 layout/base/tests/bug106855-2.html create mode 100644 layout/base/tests/bug1078327_inner.html create mode 100644 layout/base/tests/bug1080360_inner.html create mode 100644 layout/base/tests/bug1080361_inner.html create mode 100644 layout/base/tests/bug1082486-1-ref.html create mode 100644 layout/base/tests/bug1082486-1.html create mode 100644 layout/base/tests/bug1082486-2-ref.html create mode 100644 layout/base/tests/bug1082486-2.html create mode 100644 layout/base/tests/bug1093686_inner.html create mode 100644 layout/base/tests/bug1097242-1-ref.html create mode 100644 layout/base/tests/bug1097242-1.html create mode 100644 layout/base/tests/bug1109968-1-ref.html create mode 100644 layout/base/tests/bug1109968-1.html create mode 100644 layout/base/tests/bug1109968-2-ref.html create mode 100644 layout/base/tests/bug1109968-2.html create mode 100644 layout/base/tests/bug1123067-1.html create mode 100644 layout/base/tests/bug1123067-2.html create mode 100644 layout/base/tests/bug1123067-3.html create mode 100644 layout/base/tests/bug1123067-ref.html create mode 100644 layout/base/tests/bug1132768-1-ref.html create mode 100644 layout/base/tests/bug1132768-1.html create mode 100644 layout/base/tests/bug1153130_inner.html create mode 100644 layout/base/tests/bug1162990_inner_1.html create mode 100644 layout/base/tests/bug1162990_inner_2.html create mode 100644 layout/base/tests/bug1226904.html create mode 100644 layout/base/tests/bug1237236-1-ref.html create mode 100644 layout/base/tests/bug1237236-1.html create mode 100644 layout/base/tests/bug1237236-2-ref.html create mode 100644 layout/base/tests/bug1237236-2.html create mode 100644 layout/base/tests/bug1258308-1-ref.html create mode 100644 layout/base/tests/bug1258308-1.html create mode 100644 layout/base/tests/bug1258308-2-ref.html create mode 100644 layout/base/tests/bug1258308-2.html create mode 100644 layout/base/tests/bug1259949-1-ref.html create mode 100644 layout/base/tests/bug1259949-1.html create mode 100644 layout/base/tests/bug1259949-2-ref.html create mode 100644 layout/base/tests/bug1259949-2.html create mode 100644 layout/base/tests/bug1263288-ref.html create mode 100644 layout/base/tests/bug1263288.html create mode 100644 layout/base/tests/bug1263357-1-ref.html create mode 100644 layout/base/tests/bug1263357-1.html create mode 100644 layout/base/tests/bug1263357-2-ref.html create mode 100644 layout/base/tests/bug1263357-2.html create mode 100644 layout/base/tests/bug1263357-3-ref.html create mode 100644 layout/base/tests/bug1263357-3.html create mode 100644 layout/base/tests/bug1263357-4-ref.html create mode 100644 layout/base/tests/bug1263357-4.html create mode 100644 layout/base/tests/bug1263357-5-ref.html create mode 100644 layout/base/tests/bug1263357-5.html create mode 100644 layout/base/tests/bug1354478-1-ref.html create mode 100644 layout/base/tests/bug1354478-1.html create mode 100644 layout/base/tests/bug1354478-2-ref.html create mode 100644 layout/base/tests/bug1354478-2.html create mode 100644 layout/base/tests/bug1354478-3-ref.html create mode 100644 layout/base/tests/bug1354478-3.html create mode 100644 layout/base/tests/bug1354478-4-ref.html create mode 100644 layout/base/tests/bug1354478-4.html create mode 100644 layout/base/tests/bug1354478-5-ref.html create mode 100644 layout/base/tests/bug1354478-5.html create mode 100644 layout/base/tests/bug1354478-6-ref.html create mode 100644 layout/base/tests/bug1354478-6.html create mode 100644 layout/base/tests/bug1359411-ref.html create mode 100644 layout/base/tests/bug1359411.html create mode 100644 layout/base/tests/bug1415416-ref.html create mode 100644 layout/base/tests/bug1415416.html create mode 100644 layout/base/tests/bug1423331-1-ref.html create mode 100644 layout/base/tests/bug1423331-1.html create mode 100644 layout/base/tests/bug1423331-2-ref.html create mode 100644 layout/base/tests/bug1423331-2.html create mode 100644 layout/base/tests/bug1423331-3.html create mode 100644 layout/base/tests/bug1423331-4.html create mode 100644 layout/base/tests/bug1448730.html create mode 100644 layout/base/tests/bug1484094-1-ref.html create mode 100644 layout/base/tests/bug1484094-1.html create mode 100644 layout/base/tests/bug1484094-2-ref.html create mode 100644 layout/base/tests/bug1484094-2.html create mode 100644 layout/base/tests/bug1496118-ref.html create mode 100644 layout/base/tests/bug1496118.html create mode 100644 layout/base/tests/bug1506547-1.html create mode 100644 layout/base/tests/bug1506547-2.html create mode 100644 layout/base/tests/bug1506547-3.html create mode 100644 layout/base/tests/bug1506547-4-ref.html create mode 100644 layout/base/tests/bug1506547-4.html create mode 100644 layout/base/tests/bug1506547-5-ref.html create mode 100644 layout/base/tests/bug1506547-5.html create mode 100644 layout/base/tests/bug1506547-6.html create mode 100644 layout/base/tests/bug1510942-1-ref.html create mode 100644 layout/base/tests/bug1510942-1.html create mode 100644 layout/base/tests/bug1510942-2-ref.html create mode 100644 layout/base/tests/bug1510942-2.html create mode 100644 layout/base/tests/bug1516963-1-ref.html create mode 100644 layout/base/tests/bug1516963-1.html create mode 100644 layout/base/tests/bug1516963-2-ref.html create mode 100644 layout/base/tests/bug1516963-2.html create mode 100644 layout/base/tests/bug1516963-3-ref.html create mode 100644 layout/base/tests/bug1516963-3.html create mode 100644 layout/base/tests/bug1516963-4-ref.html create mode 100644 layout/base/tests/bug1516963-4.html create mode 100644 layout/base/tests/bug1516963-5-ref.html create mode 100644 layout/base/tests/bug1516963-5.html create mode 100644 layout/base/tests/bug1516963-6-ref.html create mode 100644 layout/base/tests/bug1516963-6.html create mode 100644 layout/base/tests/bug1518339-1-ref.html create mode 100644 layout/base/tests/bug1518339-1.html create mode 100644 layout/base/tests/bug1518339-2-ref.html create mode 100644 layout/base/tests/bug1518339-2.html create mode 100644 layout/base/tests/bug1524266-1-ref.html create mode 100644 layout/base/tests/bug1524266-1.html create mode 100644 layout/base/tests/bug1524266-2-ref.html create mode 100644 layout/base/tests/bug1524266-2.html create mode 100644 layout/base/tests/bug1524266-3.html create mode 100644 layout/base/tests/bug1524266-4.html create mode 100644 layout/base/tests/bug1529492-1-ref.html create mode 100644 layout/base/tests/bug1529492-1.html create mode 100644 layout/base/tests/bug1550869-1-ref.html create mode 100644 layout/base/tests/bug1550869-1a.html create mode 100644 layout/base/tests/bug1550869-1b.html create mode 100644 layout/base/tests/bug1550869-1c.html create mode 100644 layout/base/tests/bug1550869-2-ref.html create mode 100644 layout/base/tests/bug1550869-2a.html create mode 100644 layout/base/tests/bug1550869-2b.html create mode 100644 layout/base/tests/bug1550869-2c.html create mode 100644 layout/base/tests/bug1550869-2d.html create mode 100644 layout/base/tests/bug1591282-1-ref.html create mode 100644 layout/base/tests/bug1591282-1.html create mode 100644 layout/base/tests/bug1611661-ref.html create mode 100644 layout/base/tests/bug1611661.html create mode 100644 layout/base/tests/bug1634543-1-ref.html create mode 100644 layout/base/tests/bug1634543-1.html create mode 100644 layout/base/tests/bug1634543-2.html create mode 100644 layout/base/tests/bug1634543-3.html create mode 100644 layout/base/tests/bug1634543-4.html create mode 100644 layout/base/tests/bug1634743-1-ref.html create mode 100644 layout/base/tests/bug1634743-1.html create mode 100644 layout/base/tests/bug1637476-1-ref.html create mode 100644 layout/base/tests/bug1637476-1.html create mode 100644 layout/base/tests/bug1637476-2-ref.html create mode 100644 layout/base/tests/bug1637476-2.html create mode 100644 layout/base/tests/bug1637476-3-ref.html create mode 100644 layout/base/tests/bug1637476-3.html create mode 100644 layout/base/tests/bug1663475-1-ref.html create mode 100644 layout/base/tests/bug1663475-1.html create mode 100644 layout/base/tests/bug1663475-2-ref.html create mode 100644 layout/base/tests/bug1663475-2.html create mode 100644 layout/base/tests/bug1670531-1.html create mode 100644 layout/base/tests/bug1670531-2.html create mode 100644 layout/base/tests/bug1670531-3-ref.html create mode 100644 layout/base/tests/bug1670531-3.html create mode 100644 layout/base/tests/bug1670531-4.html create mode 100644 layout/base/tests/bug240933-1-ref.html create mode 100644 layout/base/tests/bug240933-1.html create mode 100644 layout/base/tests/bug240933-2.html create mode 100644 layout/base/tests/bug369950-subframe.xml create mode 100644 layout/base/tests/bug389321-1-ref.html create mode 100644 layout/base/tests/bug389321-1.html create mode 100644 layout/base/tests/bug389321-2-ref.html create mode 100644 layout/base/tests/bug389321-2.html create mode 100644 layout/base/tests/bug389321-3-ref.html create mode 100644 layout/base/tests/bug389321-3.html create mode 100644 layout/base/tests/bug450930.xhtml create mode 100644 layout/base/tests/bug482484-ref.html create mode 100644 layout/base/tests/bug482484.html create mode 100644 layout/base/tests/bug503399-ref.html create mode 100644 layout/base/tests/bug503399.html create mode 100644 layout/base/tests/bug512295-1-ref.html create mode 100644 layout/base/tests/bug512295-1.html create mode 100644 layout/base/tests/bug512295-2-ref.html create mode 100644 layout/base/tests/bug512295-2.html create mode 100644 layout/base/tests/bug558663.html create mode 100644 layout/base/tests/bug583889_inner1.html create mode 100644 layout/base/tests/bug583889_inner2.html create mode 100644 layout/base/tests/bug585922-ref.html create mode 100644 layout/base/tests/bug585922.html create mode 100644 layout/base/tests/bug597519-1-ref.html create mode 100644 layout/base/tests/bug597519-1.html create mode 100644 layout/base/tests/bug602141-1-ref.html create mode 100644 layout/base/tests/bug602141-1.html create mode 100644 layout/base/tests/bug602141-2-ref.html create mode 100644 layout/base/tests/bug602141-2.html create mode 100644 layout/base/tests/bug602141-3-ref.html create mode 100644 layout/base/tests/bug602141-3.html create mode 100644 layout/base/tests/bug602141-4-ref.html create mode 100644 layout/base/tests/bug602141-4.html create mode 100644 layout/base/tests/bug612271-1.html create mode 100644 layout/base/tests/bug612271-2.html create mode 100644 layout/base/tests/bug612271-3.html create mode 100644 layout/base/tests/bug612271-ref.html create mode 100644 layout/base/tests/bug613433-1.html create mode 100644 layout/base/tests/bug613433-2.html create mode 100644 layout/base/tests/bug613433-3.html create mode 100644 layout/base/tests/bug613433-ref.html create mode 100644 layout/base/tests/bug613807-1-ref.html create mode 100644 layout/base/tests/bug613807-1.html create mode 100644 layout/base/tests/bug632215-1.html create mode 100644 layout/base/tests/bug632215-2.html create mode 100644 layout/base/tests/bug632215-ref.html create mode 100644 layout/base/tests/bug633044-1-ref.html create mode 100644 layout/base/tests/bug633044-1.html create mode 100644 layout/base/tests/bug634406-1-ref.html create mode 100644 layout/base/tests/bug634406-1.html create mode 100644 layout/base/tests/bug644428-1-ref.html create mode 100644 layout/base/tests/bug644428-1.html create mode 100644 layout/base/tests/bug646382-1-ref.html create mode 100644 layout/base/tests/bug646382-1.html create mode 100644 layout/base/tests/bug646382-2-ref.html create mode 100644 layout/base/tests/bug646382-2.html create mode 100644 layout/base/tests/bug664087-1-ref.html create mode 100644 layout/base/tests/bug664087-1.html create mode 100644 layout/base/tests/bug664087-2-ref.html create mode 100644 layout/base/tests/bug664087-2.html create mode 100644 layout/base/tests/bug682712-1-ref.html create mode 100644 layout/base/tests/bug682712-1.html create mode 100644 layout/base/tests/bug687297_a.html create mode 100644 layout/base/tests/bug687297_b.html create mode 100644 layout/base/tests/bug687297_c.html create mode 100644 layout/base/tests/bug746993-1-ref.html create mode 100644 layout/base/tests/bug746993-1.html create mode 100644 layout/base/tests/bug851445_helper.html create mode 100644 layout/base/tests/bug923376-ref.html create mode 100644 layout/base/tests/bug923376.html create mode 100644 layout/base/tests/bug956530-1-ref.html create mode 100644 layout/base/tests/bug956530-1.html create mode 100644 layout/base/tests/bug966992-1-ref.html create mode 100644 layout/base/tests/bug966992-1.html create mode 100644 layout/base/tests/bug966992-2-ref.html create mode 100644 layout/base/tests/bug966992-2.html create mode 100644 layout/base/tests/bug970964_inner.html create mode 100644 layout/base/tests/bug970964_inner2.html create mode 100644 layout/base/tests/bug977003_inner_1.html create mode 100644 layout/base/tests/bug977003_inner_2.html create mode 100644 layout/base/tests/bug977003_inner_3.html create mode 100644 layout/base/tests/bug977003_inner_4.html create mode 100644 layout/base/tests/bug977003_inner_5.html create mode 100644 layout/base/tests/bug977003_inner_6.html create mode 100644 layout/base/tests/bug989012-1-ref.html create mode 100644 layout/base/tests/bug989012-1.html create mode 100644 layout/base/tests/bug989012-2-ref.html create mode 100644 layout/base/tests/bug989012-2.html create mode 100644 layout/base/tests/bug989012-3-ref.html create mode 100644 layout/base/tests/bug989012-3.html create mode 100644 layout/base/tests/chrome/animated.gif create mode 100644 layout/base/tests/chrome/blue-32x32.png create mode 100644 layout/base/tests/chrome/bug1041200_frame.html create mode 100644 layout/base/tests/chrome/bug1041200_window.html create mode 100644 layout/base/tests/chrome/bug1722890.html create mode 100644 layout/base/tests/chrome/bug1722890_ref.html create mode 100644 layout/base/tests/chrome/bug1769161_1.html create mode 100644 layout/base/tests/chrome/bug1769161_1_ref.html create mode 100644 layout/base/tests/chrome/bug1769161_2.html create mode 100644 layout/base/tests/chrome/bug1769161_2_ref.html create mode 100644 layout/base/tests/chrome/bug1769161_3.html create mode 100644 layout/base/tests/chrome/bug1769161_3_ref.html create mode 100644 layout/base/tests/chrome/bug1769161_4.html create mode 100644 layout/base/tests/chrome/bug1769161_4_ref.html create mode 100644 layout/base/tests/chrome/bug495648.rdf create mode 100644 layout/base/tests/chrome/bug551434_childframe.html create mode 100644 layout/base/tests/chrome/chrome.toml create mode 100644 layout/base/tests/chrome/chrome_content_integration_window.xhtml create mode 100644 layout/base/tests/chrome/color_adjust.html create mode 100644 layout/base/tests/chrome/color_adjust_ref.html create mode 100644 layout/base/tests/chrome/default_background_window.xhtml create mode 100644 layout/base/tests/chrome/dialog_with_positioning_window.xhtml create mode 100644 layout/base/tests/chrome/file_bug1018265.xhtml create mode 100644 layout/base/tests/chrome/file_bug458898.html create mode 100644 layout/base/tests/chrome/file_bug465448.html create mode 100644 layout/base/tests/chrome/frame_css_visibility_propagation.html create mode 100644 layout/base/tests/chrome/green.png create mode 100644 layout/base/tests/chrome/markA.ttf create mode 100644 layout/base/tests/chrome/markB.ttf create mode 100644 layout/base/tests/chrome/print_page_size1.html create mode 100644 layout/base/tests/chrome/print_page_size1_ref.html create mode 100644 layout/base/tests/chrome/print_page_size2.html create mode 100644 layout/base/tests/chrome/print_page_size2_ref.html create mode 100644 layout/base/tests/chrome/print_page_size3.html create mode 100644 layout/base/tests/chrome/print_page_size3_ref.html create mode 100644 layout/base/tests/chrome/print_page_size4.html create mode 100644 layout/base/tests/chrome/print_page_size4_ref.html create mode 100644 layout/base/tests/chrome/printpreview_bug1713404_ref.html create mode 100644 layout/base/tests/chrome/printpreview_bug1730091_ref.html create mode 100644 layout/base/tests/chrome/printpreview_bug396024_helper.xhtml create mode 100644 layout/base/tests/chrome/printpreview_bug482976_helper.xhtml create mode 100644 layout/base/tests/chrome/printpreview_downloadable_font.html create mode 100644 layout/base/tests/chrome/printpreview_downloadable_font_in_iframe.html create mode 100644 layout/base/tests/chrome/printpreview_downloadable_font_in_iframe_ref.html create mode 100644 layout/base/tests/chrome/printpreview_downloadable_font_ref.html create mode 100644 layout/base/tests/chrome/printpreview_font_api.html create mode 100644 layout/base/tests/chrome/printpreview_font_api_ref.html create mode 100644 layout/base/tests/chrome/printpreview_font_mozprintcallback.html create mode 100644 layout/base/tests/chrome/printpreview_font_mozprintcallback_ref.html create mode 100644 layout/base/tests/chrome/printpreview_helper.xhtml create mode 100644 layout/base/tests/chrome/printpreview_image_select.html create mode 100644 layout/base/tests/chrome/printpreview_image_select_ref.html create mode 100644 layout/base/tests/chrome/printpreview_images.html create mode 100644 layout/base/tests/chrome/printpreview_images_ref.html create mode 100644 layout/base/tests/chrome/printpreview_images_sw.html create mode 100644 layout/base/tests/chrome/printpreview_images_sw.js create mode 100644 layout/base/tests/chrome/printpreview_images_sw_ref.html create mode 100644 layout/base/tests/chrome/printpreview_mask.html create mode 100644 layout/base/tests/chrome/printpreview_mixed_page_size_001.html create mode 100644 layout/base/tests/chrome/printpreview_mixed_page_size_002.html create mode 100644 layout/base/tests/chrome/printpreview_pps16.html create mode 100644 layout/base/tests/chrome/printpreview_pps16_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps2.html create mode 100644 layout/base/tests/chrome/printpreview_pps2_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps4.html create mode 100644 layout/base/tests/chrome/printpreview_pps4_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps6.html create mode 100644 layout/base/tests/chrome/printpreview_pps6_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps9.html create mode 100644 layout/base/tests/chrome/printpreview_pps9_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw2.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw2_no_margin_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw2_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw4.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw4_ref.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw9.html create mode 100644 layout/base/tests/chrome/printpreview_pps_uw9_ref.html create mode 100644 layout/base/tests/chrome/printpreview_prettyprint.xml create mode 100644 layout/base/tests/chrome/printpreview_prettyprint_ref.xhtml create mode 100644 layout/base/tests/chrome/printpreview_quirks.html create mode 100644 layout/base/tests/chrome/printpreview_quirks_ref.html create mode 100644 layout/base/tests/chrome/red.png create mode 100644 layout/base/tests/chrome/test_bug1018265.xhtml create mode 100644 layout/base/tests/chrome/test_bug1041200.xhtml create mode 100644 layout/base/tests/chrome/test_bug396367-1.html create mode 100644 layout/base/tests/chrome/test_bug396367-2.html create mode 100644 layout/base/tests/chrome/test_bug420499.xhtml create mode 100644 layout/base/tests/chrome/test_bug458898.html create mode 100644 layout/base/tests/chrome/test_bug465448.xhtml create mode 100644 layout/base/tests/chrome/test_bug514660.xhtml create mode 100644 layout/base/tests/chrome/test_bug533845.xhtml create mode 100644 layout/base/tests/chrome/test_bug551434.html create mode 100644 layout/base/tests/chrome/test_bug708062.html create mode 100644 layout/base/tests/chrome/test_bug812817.xhtml create mode 100644 layout/base/tests/chrome/test_chrome_content_integration.xhtml create mode 100644 layout/base/tests/chrome/test_color_scheme_browser.xhtml create mode 100644 layout/base/tests/chrome/test_css_visibility_propagation.xhtml create mode 100644 layout/base/tests/chrome/test_default_background.xhtml create mode 100644 layout/base/tests/chrome/test_dialog_with_positioning.html create mode 100644 layout/base/tests/chrome/test_document_adopted_styles.html create mode 100644 layout/base/tests/chrome/test_document_adopted_styles_ref.html create mode 100644 layout/base/tests/chrome/test_fixed_bg_scrolling_repaints.html create mode 100644 layout/base/tests/chrome/test_getClientRectsAndTexts.html create mode 100644 layout/base/tests/chrome/test_get_printer_basic_attributes.html create mode 100644 layout/base/tests/chrome/test_get_printer_orientation.html create mode 100644 layout/base/tests/chrome/test_get_printer_paper_sizes.html create mode 100644 layout/base/tests/chrome/test_prerendered_transforms.html create mode 100644 layout/base/tests/chrome/test_printer_default_settings.html create mode 100644 layout/base/tests/chrome/test_printpreview.xhtml create mode 100644 layout/base/tests/chrome/test_printpreview_bug396024.xhtml create mode 100644 layout/base/tests/chrome/test_printpreview_bug482976.xhtml create mode 100644 layout/base/tests/chrome/test_scrolling_repaints.html create mode 100644 layout/base/tests/chrome/test_shadow_root_adopted_styles.html create mode 100644 layout/base/tests/chrome/test_shadow_root_adopted_styles_ref.html create mode 100644 layout/base/tests/chrome/test_shared_adopted_styles.html create mode 100644 layout/base/tests/chrome/test_shared_adopted_styles_ref.html create mode 100644 layout/base/tests/chrome/test_will_change.html create mode 100644 layout/base/tests/chrome/window_css_visibility_propagation-1.xhtml create mode 100644 layout/base/tests/chrome/window_css_visibility_propagation-2.xhtml create mode 100644 layout/base/tests/chrome/window_css_visibility_propagation-3.html create mode 100644 layout/base/tests/chrome/window_css_visibility_propagation-4.html create mode 100644 layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input-ref.html create mode 100644 layout/base/tests/collapse-selection-into-editing-host-during-blur-of-input.html create mode 100644 layout/base/tests/file_bug607529-1.html create mode 100644 layout/base/tests/file_bug607529.html create mode 100644 layout/base/tests/file_bug842853-frame.html create mode 100644 layout/base/tests/file_bug842853.html create mode 100644 layout/base/tests/file_bug842853.sjs create mode 100644 layout/base/tests/file_dynamic_toolbar_max_height.html create mode 100644 layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame1.html create mode 100644 layout/base/tests/file_getBoxQuads_convertPointRectQuad_frame2.html create mode 100644 layout/base/tests/file_lazyload_telemetry.html create mode 100644 layout/base/tests/file_stylesheet_change_events.html create mode 100644 layout/base/tests/file_synthmousemove.html create mode 100644 layout/base/tests/file_zoom_restore_bfcache.html create mode 100644 layout/base/tests/helper_bug1701027-1.html create mode 100644 layout/base/tests/helper_bug1701027-2.html create mode 100644 layout/base/tests/helper_synthmousemove.html create mode 100644 layout/base/tests/image_rgrg-256x256.png create mode 100644 layout/base/tests/input-invalid-ref.html create mode 100644 layout/base/tests/input-maxlength-invalid-change.html create mode 100644 layout/base/tests/input-maxlength-ui-invalid-change.html create mode 100644 layout/base/tests/input-maxlength-ui-valid-change.html create mode 100644 layout/base/tests/input-maxlength-valid-before-change.html create mode 100644 layout/base/tests/input-maxlength-valid-change.html create mode 100644 layout/base/tests/input-minlength-invalid-change.html create mode 100644 layout/base/tests/input-minlength-ui-invalid-change.html create mode 100644 layout/base/tests/input-minlength-ui-valid-change.html create mode 100644 layout/base/tests/input-minlength-valid-before-change.html create mode 100644 layout/base/tests/input-minlength-valid-change.html create mode 100644 layout/base/tests/input-password-RTL-input-ref.html create mode 100644 layout/base/tests/input-password-RTL-input.html create mode 100644 layout/base/tests/input-password-remask-ref.html create mode 100644 layout/base/tests/input-password-remask.html create mode 100644 layout/base/tests/input-password-unmask-around-emoji-ref.html create mode 100644 layout/base/tests/input-password-unmask-around-emoji.html create mode 100644 layout/base/tests/input-password-unmask-ref.html create mode 100644 layout/base/tests/input-password-unmask.html create mode 100644 layout/base/tests/input-stoppropagation-ref.html create mode 100644 layout/base/tests/input-stoppropagation.html create mode 100644 layout/base/tests/input-ui-valid-ref.html create mode 100644 layout/base/tests/input-valid-ref.html create mode 100644 layout/base/tests/interlinePosition-after-Selection-addRange-ref.html create mode 100644 layout/base/tests/interlinePosition-after-Selection-addRange.html create mode 100644 layout/base/tests/marionette/manifest.toml create mode 100644 layout/base/tests/marionette/selection.py create mode 100644 layout/base/tests/marionette/test_accessiblecaret_cursor_mode.py create mode 100644 layout/base/tests/marionette/test_accessiblecaret_selection_mode.py create mode 100644 layout/base/tests/mochitest.toml create mode 100644 layout/base/tests/multi-range-script-select-ref.html create mode 100644 layout/base/tests/multi-range-script-select.html create mode 100644 layout/base/tests/multi-range-user-select-ref.html create mode 100644 layout/base/tests/multi-range-user-select.html create mode 100644 layout/base/tests/partial.png create mode 100644 layout/base/tests/preserve3d_sorting_hit_testing2_iframe.html create mode 100644 layout/base/tests/preserve3d_sorting_hit_testing_iframe.html create mode 100644 layout/base/tests/resize_flush_iframe.html create mode 100644 layout/base/tests/scroll_into_view_in_child.html create mode 100644 layout/base/tests/scroll_selection_into_view_window.html create mode 100644 layout/base/tests/scroll_selection_into_view_window_frame.html create mode 100644 layout/base/tests/selection-utils.js create mode 100644 layout/base/tests/sendimagenevercomplete.sjs create mode 100644 layout/base/tests/stylesheet_change_events.css create mode 100644 layout/base/tests/test_accessiblecaret_magnifier.html create mode 100644 layout/base/tests/test_after_paint_pref.html create mode 100644 layout/base/tests/test_border_radius_hit_testing.html create mode 100644 layout/base/tests/test_bug1078327.html create mode 100644 layout/base/tests/test_bug1080360.html create mode 100644 layout/base/tests/test_bug1080361.html create mode 100644 layout/base/tests/test_bug1093686.html create mode 100644 layout/base/tests/test_bug1120705.html create mode 100644 layout/base/tests/test_bug114649.html create mode 100644 layout/base/tests/test_bug1153130.html create mode 100644 layout/base/tests/test_bug1162990.html create mode 100644 layout/base/tests/test_bug1216483.html create mode 100644 layout/base/tests/test_bug1226904.html create mode 100644 layout/base/tests/test_bug1246622.html create mode 100644 layout/base/tests/test_bug1278021.html create mode 100644 layout/base/tests/test_bug1448730.html create mode 100644 layout/base/tests/test_bug1515822.html create mode 100644 layout/base/tests/test_bug1550869_video.html create mode 100644 layout/base/tests/test_bug1714640.html create mode 100644 layout/base/tests/test_bug1756118.html create mode 100644 layout/base/tests/test_bug332655-1.html create mode 100644 layout/base/tests/test_bug332655-2.html create mode 100644 layout/base/tests/test_bug369950.html create mode 100644 layout/base/tests/test_bug370436.html create mode 100644 layout/base/tests/test_bug386575.xhtml create mode 100644 layout/base/tests/test_bug388019.html create mode 100644 layout/base/tests/test_bug394057.html create mode 100644 layout/base/tests/test_bug399284.html create mode 100644 layout/base/tests/test_bug399951.html create mode 100644 layout/base/tests/test_bug404209.xhtml create mode 100644 layout/base/tests/test_bug416896.html create mode 100644 layout/base/tests/test_bug423523.html create mode 100644 layout/base/tests/test_bug435293-interaction.html create mode 100644 layout/base/tests/test_bug435293-scale.html create mode 100644 layout/base/tests/test_bug435293-skew.html create mode 100644 layout/base/tests/test_bug449781.html create mode 100644 layout/base/tests/test_bug450930.xhtml create mode 100644 layout/base/tests/test_bug469170.html create mode 100644 layout/base/tests/test_bug471126.html create mode 100644 layout/base/tests/test_bug499538-1.html create mode 100644 layout/base/tests/test_bug514127.html create mode 100644 layout/base/tests/test_bug518777.html create mode 100644 layout/base/tests/test_bug548545.xhtml create mode 100644 layout/base/tests/test_bug558663.html create mode 100644 layout/base/tests/test_bug559499.html create mode 100644 layout/base/tests/test_bug569520.html create mode 100644 layout/base/tests/test_bug582181-1.html create mode 100644 layout/base/tests/test_bug582181-2.html create mode 100644 layout/base/tests/test_bug582771.html create mode 100644 layout/base/tests/test_bug583889.html create mode 100644 layout/base/tests/test_bug588174.html create mode 100644 layout/base/tests/test_bug603550.html create mode 100644 layout/base/tests/test_bug607529.html create mode 100644 layout/base/tests/test_bug629838.html create mode 100644 layout/base/tests/test_bug644768.html create mode 100644 layout/base/tests/test_bug646757.html create mode 100644 layout/base/tests/test_bug66619.html create mode 100644 layout/base/tests/test_bug667512.html create mode 100644 layout/base/tests/test_bug677878.html create mode 100644 layout/base/tests/test_bug687297.html create mode 100644 layout/base/tests/test_bug696020.html create mode 100644 layout/base/tests/test_bug718809.html create mode 100644 layout/base/tests/test_bug725426.html create mode 100644 layout/base/tests/test_bug731777.html create mode 100644 layout/base/tests/test_bug749186.html create mode 100644 layout/base/tests/test_bug761572.html create mode 100644 layout/base/tests/test_bug770106.html create mode 100644 layout/base/tests/test_bug842853-2.html create mode 100644 layout/base/tests/test_bug842853.html create mode 100644 layout/base/tests/test_bug849219.html create mode 100644 layout/base/tests/test_bug851445.html create mode 100644 layout/base/tests/test_bug851485.html create mode 100644 layout/base/tests/test_bug858459.html create mode 100644 layout/base/tests/test_bug93077-1.html create mode 100644 layout/base/tests/test_bug93077-2.html create mode 100644 layout/base/tests/test_bug93077-3.html create mode 100644 layout/base/tests/test_bug93077-4.html create mode 100644 layout/base/tests/test_bug93077-5.html create mode 100644 layout/base/tests/test_bug93077-6.html create mode 100644 layout/base/tests/test_bug970964.html create mode 100644 layout/base/tests/test_bug977003.html create mode 100644 layout/base/tests/test_bug990340.html create mode 100644 layout/base/tests/test_bug993936.html create mode 100644 layout/base/tests/test_caret_browsing_around_form_controls.html create mode 100644 layout/base/tests/test_dynamic_toolbar_max_height.html create mode 100644 layout/base/tests/test_emulateMedium.html create mode 100644 layout/base/tests/test_emulate_color_scheme.html create mode 100644 layout/base/tests/test_event_target_iframe_oop.html create mode 100644 layout/base/tests/test_event_target_radius.html create mode 100644 layout/base/tests/test_frame_reconstruction_body_table.html create mode 100644 layout/base/tests/test_frame_reconstruction_body_writing_mode.html create mode 100644 layout/base/tests/test_frame_reconstruction_for_column_span.html create mode 100644 layout/base/tests/test_frame_reconstruction_for_pseudo_elements.html create mode 100644 layout/base/tests/test_frame_reconstruction_for_svg_transforms.html create mode 100644 layout/base/tests/test_frame_reconstruction_scroll_restore.html create mode 100644 layout/base/tests/test_getBoxQuads_convertPointRectQuad.html create mode 100644 layout/base/tests/test_getClientRects_emptytext.html create mode 100644 layout/base/tests/test_mozPaintCount.html create mode 100644 layout/base/tests/test_partialbg.html create mode 100644 layout/base/tests/test_preserve3d_sorting_hit_testing.html create mode 100644 layout/base/tests/test_preserve3d_sorting_hit_testing2.html create mode 100644 layout/base/tests/test_refreshDriver_hasPendingTick.html create mode 100644 layout/base/tests/test_reftests_with_caret.html create mode 100644 layout/base/tests/test_resize_flush.html create mode 100644 layout/base/tests/test_scroll_event_ordering.html create mode 100644 layout/base/tests/test_scroll_into_view_in_oopif.html create mode 100644 layout/base/tests/test_scroll_selection_into_view.html create mode 100644 layout/base/tests/test_scroll_space_no_range_overflow_scroll.html create mode 100644 layout/base/tests/test_synthmousemove.html create mode 100644 layout/base/tests/test_transformed_scrolling_repaints.html create mode 100644 layout/base/tests/test_transformed_scrolling_repaints_2.html create mode 100644 layout/base/tests/test_transformed_scrolling_repaints_3.html create mode 100644 layout/base/tests/test_visual_viewport_in_oopif.html create mode 100644 layout/base/tests/test_zoom_restore_bfcache.html create mode 100644 layout/base/tests/textarea-invalid-ref.html create mode 100644 layout/base/tests/textarea-maxlength-invalid-change.html create mode 100644 layout/base/tests/textarea-maxlength-ui-invalid-change.html create mode 100644 layout/base/tests/textarea-maxlength-ui-valid-change.html create mode 100644 layout/base/tests/textarea-maxlength-valid-before-change.html create mode 100644 layout/base/tests/textarea-maxlength-valid-change.html create mode 100644 layout/base/tests/textarea-minlength-invalid-change.html create mode 100644 layout/base/tests/textarea-minlength-ui-invalid-change.html create mode 100644 layout/base/tests/textarea-minlength-ui-valid-change.html create mode 100644 layout/base/tests/textarea-minlength-valid-before-change.html create mode 100644 layout/base/tests/textarea-minlength-valid-change.html create mode 100644 layout/base/tests/textarea-valid-ref.html create mode 100644 layout/base/tests/transformed_scrolling_repaints_3_window.html create mode 100644 layout/base/tests/transformed_scrolling_repaints_3_window_frame.html create mode 100644 layout/base/tests/visual_viewport_in_child.html (limited to 'layout/base') 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 + //
<- CaretElement() + // <#shadow-root> + // + //
<- TextOverlayElement() + //
<- 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 linkNodeInfo = doc->NodeInfoManager()->GetNodeInfo( + nsGkAtoms::link, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE); + RefPtr 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 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 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 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(docShell); + + if (StaticPrefs::layout_accessiblecaret_use_long_tap_injector()) { + mLongTapInjectorTimer = NS_NewTimer(); + } + + mManager = MakeUnique(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 self = + static_cast(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 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 mManager; + + WeakPtr 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 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 + +#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(aPresShell) : nullptr, + aPresShell ? MakeUnique(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( + 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 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 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 GetHitTestOptions() { + EnumSet 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 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 saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing); + mLayoutFlusher.mAllowFlushing = false; + + Maybe 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 saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing); + mLayoutFlusher.mAllowFlushing = false; + + Maybe 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 saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing); + mLayoutFlusher.mAllowFlushing = false; + + Maybe 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 saveAllowFlushingLayout(mLayoutFlusher.mAllowFlushing); + mLayoutFlusher.mAllowFlushing = false; + + Maybe 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 fs = GetFrameSelection(); + if (!fs) { + return nullptr; + } + return fs->GetSelection(SelectionType::eNormal); +} + +already_AddRefed AccessibleCaretManager::GetFrameSelection() + const { + if (!mPresShell) { + return nullptr; + } + + // Prevent us from touching the nsFrameSelection associated with other + // PresShell. + RefPtr fs = mPresShell->GetLastFocusedFrameSelection(); + if (!fs || fs->GetPresShell() != mPresShell) { + return nullptr; + } + + return fs.forget(); +} + +nsAutoString AccessibleCaretManager::StringifiedSelection() const { + nsAutoString str; + RefPtr 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 fm = nsFocusManager::GetFocusManager(); + MOZ_ASSERT(fm); + + if (aFrame) { + nsIContent* focusableContent = aFrame->GetContent(); + MOZ_ASSERT(focusableContent, "Focusable frame must have content!"); + RefPtr focusableElement = Element::FromNode(focusableContent); + fm->SetFocus(focusableElement, nsIFocusManager::FLAG_BYLONGPRESS); + } else if (nsCOMPtr 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 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 fs = GetFrameSelection(); + if (fs) { + fs->SetDragState(aState); + } +} + +bool AccessibleCaretManager::IsPhoneNumber(const nsAString& aCandidate) const { + RefPtr 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 = 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 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 fs = GetFrameSelection(); + if (fs) { + fs->MaintainSelection(eSelectNoAmount); + } +} + +void AccessibleCaretManager::LayoutFlusher::MaybeFlush( + const PresShell& aPresShell) { + if (mAllowFlushing) { + AutoRestore 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 startNode; + RefPtr endNode; + int32_t nodeOffset = 0; + CaretAssociationHint hint; + + RefPtr 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 startContent = nsIContent::FromNodeOrNull(startNode); + uint32_t outOffset = 0; + nsIFrame* startFrame = SelectionMovementUtils::GetFrameForNodeOffset( + startContent, nodeOffset, hint, &outOffset); + *aOutOffset = static_cast(outOffset); + + if (!startFrame) { + ErrorResult err; + RefPtr 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 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 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 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 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 fs = GetFrameSelection(); + MOZ_ASSERT(fs); + fs->StartAutoScrollTimer(capturingFrame, ptInScrolled, kAutoScrollTimerDelay); +} + +void AccessibleCaretManager::StopSelectionAutoScrollTimer() const { + RefPtr 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 = 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 event = CaretStateChangedEvent::Constructor( + doc, u"mozcaretstatechanged"_ns, init); + event->SetTrusted(true); + + AC_LOG("%s: reason %" PRIu32 ", collapsed %d, caretVisible %" PRIu32, + __FUNCTION__, static_cast(init.mReason), init.mCollapsed, + static_cast(init.mCaretVisible)); + + (new AsyncEventDispatcher(doc, event.forget(), ChromeOnlyDispatch::eYes)) + ->PostDOMEvent(); +} + +AccessibleCaretManager::Carets::Carets(UniquePtr aFirst, + UniquePtr 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; + + 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 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 aFirst, + UniquePtr 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 mFirst; + + // Second caret is used solely in selection mode, and is attached to + // selection highlight as the right caret. + UniquePtr 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 aCause, + const Maybe& 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 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 mCause; + Maybe 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 +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(aFrame, aWM, aGroup); +} + +nscoord Baseline::SynthesizeBOffsetFromPaddingBox(const nsIFrame* aFrame, + WritingMode aWM, + BaselineSharingGroup aGroup) { + return SynthesizeBOffsetFromInnerBox(aFrame, aWM, aGroup); +} + +nscoord Baseline::SynthesizeBOffsetFromBorderBox(const nsIFrame* aFrame, + WritingMode aWM, + BaselineSharingGroup aGroup) { + return SynthesizeBOffsetFromInnerBox(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( + 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(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 + +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& 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 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, ContainStyleScope> mScopes; + nsTHashSet> 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 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 + +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( + 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( + aContent->GetProperty(nsGkAtoms::DisplayPort)); + *aOutMarginsData = static_cast( + 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( + 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( + 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); + + 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 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( + 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); +} + +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(aFrame); + nsIFrame* oof = placeholder->GetOutOfFlowFrame(); + if (oof && !nsLayoutUtils::IsPopup(oof) && + MaybeCreateDisplayPortInFirstScrollFrameEncountered(oof, aBuilder)) { + return true; + } + } else if (aFrame->IsSubDocumentFrame()) { + PresShell* presShell = static_cast(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 DisplayPortUtils::GetRootDisplayportBase(PresShell* aPresShell) { + DebugOnly 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 +#include + +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 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 +struct FramePropertyDescriptor : public FramePropertyDescriptorUntyped { + typedef void Destructor(T* aPropertyValue); + typedef void DestructorWithFrame(const nsIFrame* aFrame, T* aPropertyValue); + + template + static constexpr const FramePropertyDescriptor NewWithDestructor() { + return {Destruct, nullptr}; + } + + template + static constexpr const FramePropertyDescriptor + NewWithDestructorWithFrame() { + return {nullptr, DestructWithFrame}; + } + + static constexpr const FramePropertyDescriptor NewWithoutDestructor() { + return {nullptr, nullptr}; + } + + private: + constexpr FramePropertyDescriptor(UntypedDestructor* aDtor, + UntypedDestructorWithFrame* aDtorWithFrame) + : FramePropertyDescriptorUntyped(aDtor, aDtorWithFrame) {} + + template + static void Destruct(void* aPropertyValue) { + Dtor(static_cast(aPropertyValue)); + } + + template + static void DestructWithFrame(const nsIFrame* aFrame, void* aPropertyValue) { + Dtor(aFrame, static_cast(aPropertyValue)); + } +}; + +// SmallValueHolder 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 +class SmallValueHolder; + +namespace detail { + +template +struct FramePropertyTypeHelper { + typedef T* Type; +}; +template +struct FramePropertyTypeHelper> { + 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 + using Descriptor = const FramePropertyDescriptor*; + using UntypedDescriptor = const FramePropertyDescriptorUntyped*; + + template + using PropertyType = typename detail::FramePropertyTypeHelper::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 + void Set(Descriptor aProperty, PropertyType aValue, + const nsIFrame* aFrame) { + uint64_t v = ReinterpretHelper::ToInternalValue(aValue); + SetInternal(aProperty, v, aFrame); + } + + /** + * Add a property value; the descriptor MUST NOT already be present. + */ + template + void Add(Descriptor aProperty, PropertyType aValue) { + MOZ_ASSERT(!Has(aProperty), "duplicate frame property"); + uint64_t v = ReinterpretHelper::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 + bool Has(Descriptor 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 + PropertyType Get(Descriptor aProperty, + bool* aFoundResult = nullptr) const { + uint64_t v = GetInternal(aProperty, aFoundResult); + return ReinterpretHelper::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 + PropertyType Take(Descriptor aProperty, bool* aFoundResult = nullptr) { + uint64_t v = TakeInternal(aProperty, aFoundResult); + return ReinterpretHelper::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 + void Remove(Descriptor aProperty, const nsIFrame* aFrame) { + RemoveInternal(aProperty, aFrame); + } + + /** + * Call @aFunction for each property or until @aFunction returns false. + */ + template + 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 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 + struct ReinterpretHelper { + static_assert(sizeof(PropertyType) <= sizeof(uint64_t), + "size of the value must never be larger than 64 bits"); + + static uint64_t ToInternalValue(PropertyType aValue) { + uint64_t v = 0; + memcpy(&v, &aValue, sizeof(aValue)); + return v; + } + + static PropertyType FromInternalValue(uint64_t aInternalValue) { + PropertyType 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::FromInternalValue(mValue)); + } else if (mProperty->mDestructorWithFrame) { + mProperty->mDestructorWithFrame( + aFrame, ReinterpretHelper::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 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::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::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 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 observerService = + services::GetObserverService()) { + observerService->AddObserver(aObserver, aTopic, aOwnsWeak); + } +} + +void GeckoMVMContext::RemoveObserver(nsIObserver* aObserver, + const char* aTopic) { + if (nsCOMPtr 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 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 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 CalculateScrollableRectForRSF() const override; + bool IsResolutionUpdatedByApz() const override; + LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() + const override; + Maybe 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 mDocument; + // raw ref since the presShell owns this + PresShell* MOZ_NON_OWNING_REF mPresShell; + nsCOMPtr 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(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& 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& 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 >& 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 >& 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 >& 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 >& 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 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 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 result = new DOMQuad(aTo->GetParentObject().mObject, points); + return result.forget(); +} + +already_AddRefed 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 result = new DOMQuad(aTo->GetParentObject().mObject, points); + return result.forget(); +} + +already_AddRefed 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 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 >& aResult, + dom::CallerType aCallerType, ErrorResult& aRv); + +void GetBoxQuadsFromWindowOrigin(nsINode* aNode, + const dom::BoxQuadOptions& aOptions, + nsTArray >& aResult, + ErrorResult& aRv); + +already_AddRefed ConvertQuadFromNode( + nsINode* aTo, dom::DOMQuad& aQuad, const GeometryNode& aFrom, + const dom::ConvertCoordinateOptions& aOptions, dom::CallerType aCallerType, + ErrorResult& aRv); + +already_AddRefed ConvertRectFromNode( + nsINode* aTo, dom::DOMRectReadOnly& aRect, const GeometryNode& aFrom, + const dom::ConvertCoordinateOptions& aOptions, dom::CallerType aCallerType, + ErrorResult& aRv); + +already_AddRefed 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; + +/** + * 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; + +} // 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(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; +using LayoutFlushCount = + EnumeratedArray; + +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 CalculateScrollableRectForRSF() const = 0; + virtual bool IsResolutionUpdatedByApz() const = 0; + virtual LayoutDeviceMargin ScrollbarAreaToExcludeFromCompositionBounds() + const = 0; + virtual Maybe 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 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( + 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( + 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( + mContext->CSSToDevPixelScale() * aResolution / ParentLayerToLayerScale(1), + PixelCastJustification::ScreenIsParentLayerForRoot); +} + +LayoutDeviceToLayerScale MobileViewportManager::ZoomToResolution( + const CSSToScreenScale& aZoom) const { + return ViewTargetAs( + aZoom, PixelCastJustification::ScreenIsParentLayerForRoot) / + mContext->CSSToDevPixelScale() * ParentLayerToLayerScale(1); +} + +void MobileViewportManager::UpdateResolutionForFirstPaint( + const CSSSize& aViewportSize) { + ScreenIntSize displaySize = ViewAs( + 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 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& aDisplayWidthChangeRatio) { + ScreenIntSize displaySize = ViewAs( + 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 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( + 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( + 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( + mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); + + if (displaySize.width == 0 || displaySize.height == 0) { + return; + } + + UpdateVisualViewportSize(displaySize, GetZoom()); +} + +void MobileViewportManager::UpdateSizesBeforeReflow() { + if (Maybe 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( + 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 displayWidthChangeRatio; + if (Maybe 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( + 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 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 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( + 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( + mDisplaySize, PixelCastJustification::LayoutDeviceIsScreenForBounds); + return ViewAs( + 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& 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 mContext; + ManagerType mManagerType; + bool mIsFirstPaint; + bool mPainted; + mozilla::LayoutDeviceIntSize mDisplaySize; + mozilla::CSSSize mMobileViewportSize; + mozilla::Maybe mRestoreResolution; + mozilla::Maybe 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 + +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(&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 MotionPathUtils::ComputeBorderRadii( + const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox) { + const nsRect insetRect = ShapeUtils::ComputeInsetRect( + StyleRect::WithAllSides(LengthPercentage::Zero()), + aCoordBox); + nsTArray 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::epsilon()) { + // For 0deg (or 180deg), we use |b| directly. + return static_cast(b); + } + + if (cost < std::numeric_limits::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(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 " 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 to position a 0x0 object area within + // the box’s containing block. + return ShapeUtils::ComputePosition(aAtPosition.AsPosition(), aCoordBox); + } + + MOZ_ASSERT(aAtPosition.IsAuto(), "\"at \" 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 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 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(cos(directionAngle)), + usedDistance * static_cast(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 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( + (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 BuildSimpleInsetPath( + const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox, + gfx::PathBuilder* aPathBuilder) { + if (!aPathBuilder) { + return nullptr; + } + + const nsRect insetRect = ShapeUtils::ComputeInsetRect( + StyleRect::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 BuildDefaultPathForURL( + gfx::PathBuilder* aBuilder) { + if (!aBuilder) { + return nullptr; + } + + Array 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(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 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 " 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 builder = MotionPathUtils::GetPathBuilder(); + + if (offsetPath.IsUrl()) { + dom::SVGGeometryElement* element = + SVGObserverUtils::GetAndObserveGeometry(const_cast(aFrame)); + if (!element) { + // Note: This behaves as path("m 0 0") (a ). + RefPtr path = BuildDefaultPathForURL(builder); + // FIXME: Bug 1836847. Once we support "at " 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 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 " || ". + MOZ_ASSERT(offsetPath.IsBasicShapeOrCoordBox()); + + const nsStyleDisplay* disp = aFrame->StyleDisplay(); + RefPtr 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 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 path = aCachedMotionPath; + if (!path) { + RefPtr builder = + MotionPathUtils::GetCompositorPathBuilder(); + path = MotionPathUtils::BuildSVGPath(pathData, builder); + } + // FIXME: Bug 1836847. Once we support "at " for path(), we have + // to give it the current box position. + return OffsetPathData::Shape(path.forget(), {}, IsClosedLoop(pathData)); + } + + // The rest part is to handle " || ". + MOZ_ASSERT(aOffsetPath.IsBasicShapeOrCoordBox()); + + const nsRect& coordBox = aMotionPathData.coordBox(); + if (coordBox.IsEmpty()) { + return OffsetPathData::None(); + } + + RefPtr builder = + MotionPathUtils::GetCompositorPathBuilder(); + if (!builder) { + return OffsetPathData::None(); + } + + RefPtr path; + if (aOffsetPath.IsCoordBox()) { + const nsRect insetRect = ShapeUtils::ComputeInsetRect( + StyleRect::WithAllSides(LengthPercentage::Zero()), + coordBox); + const nsTArray& 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 MotionPathUtils::ResolveMotionPath( + const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance, + const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor, + const StyleOffsetPosition* aPosition, + const Maybe& 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 MotionPathUtils::BuildSVGPath( + const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder) { + if (!aPathBuilder) { + return nullptr; + } + + const Span& path = aPath._0.AsSpan(); + return SVGPathData::BuildPath(path, aPathBuilder, StyleStrokeLinecap::Butt, + 0.0); +} + +/* static */ +already_AddRefed 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 " 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 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 builder = + gfxPlatform::GetPlatform() + ->ScreenReferenceDrawTarget() + ->CreatePathBuilder(gfx::FillRule::FILL_WINDING); + return builder.forget(); +} + +/* static */ +already_AddRefed MotionPathUtils::GetCompositorPathBuilder() { + // FIXME: Perhaps we need a PathBuilder which is independent on the backend. + RefPtr 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 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&& 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&& 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 ComputeBorderRadii( + const StyleBorderRadius& aBorderRadius, const nsRect& aCoordBox); + + /** + * Generate the motion path transform result. This function may be called on + * the compositor thread. + */ + static Maybe 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 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 ResolveMotionPath( + const StyleOffsetPath* aPath, const StyleLengthPercentage* aDistance, + const StyleOffsetRotate* aRotate, const StylePositionOrAuto* aAnchor, + const StyleOffsetPosition* aPosition, + const Maybe& 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 BuildSVGPath( + const StyleSVGPathData& aPath, gfx::PathBuilder* aPathBuilder); + + /** + * Build a gfx::Path from the computed basic shape. + */ + static already_AddRefed 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 GetPathBuilder(); + + /** + * Get a path builder for compositor. + */ + static already_AddRefed 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(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 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 + +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 (,